// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/utility/cloud_print/pwg_encoder.h"
#include <algorithm>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/utility/cloud_print/bitmap_image.h"
#include "net/base/big_endian.h"
namespace cloud_print {
namespace {
const uint32 kBitsPerColor = 8;
const uint32 kColorSpace = 19; // sRGB.
const uint32 kColorOrder = 0; // chunky.
const uint32 kNumColors = 3;
const uint32 kBitsPerPixel = kNumColors * kBitsPerColor;
const char kPwgKeyword[] = "RaS2";
const uint32 kHeaderSize = 1796;
const uint32 kHeaderHwResolutionHorizontal = 276;
const uint32 kHeaderHwResolutionVertical = 280;
const uint32 kHeaderCupsWidth = 372;
const uint32 kHeaderCupsHeight = 376;
const uint32 kHeaderCupsBitsPerColor = 384;
const uint32 kHeaderCupsBitsPerPixel = 388;
const uint32 kHeaderCupsBytesPerLine = 392;
const uint32 kHeaderCupsColorOrder = 396;
const uint32 kHeaderCupsColorSpace = 400;
const uint32 kHeaderCupsNumColors = 420;
const uint32 kHeaderPwgTotalPageCount = 452;
const int kPwgMaxPackedRows = 256;
const int kPwgMaxPackedPixels = 128;
} // namespace
PwgEncoder::PwgEncoder() {}
inline void encodePixelFromRGBA(const uint8* pixel, std::string* output) {
output->push_back(static_cast<char>(pixel[0]));
output->push_back(static_cast<char>(pixel[1]));
output->push_back(static_cast<char>(pixel[2]));
}
inline void encodePixelFromBGRA(const uint8* pixel, std::string* output) {
output->push_back(static_cast<char>(pixel[2]));
output->push_back(static_cast<char>(pixel[1]));
output->push_back(static_cast<char>(pixel[0]));
}
void PwgEncoder::EncodeDocumentHeader(std::string* output) const {
output->clear();
output->append(kPwgKeyword, 4);
}
void PwgEncoder::EncodePageHeader(const BitmapImage& image, const uint32 dpi,
const uint32 total_pages,
std::string* output) const {
char header[kHeaderSize];
memset(header, 0, kHeaderSize);
net::WriteBigEndian<uint32>(header + kHeaderHwResolutionHorizontal, dpi);
net::WriteBigEndian<uint32>(header + kHeaderHwResolutionVertical, dpi);
net::WriteBigEndian<uint32>(header + kHeaderCupsWidth, image.size().width());
net::WriteBigEndian<uint32>(header + kHeaderCupsHeight,
image.size().height());
net::WriteBigEndian<uint32>(header + kHeaderCupsBitsPerColor, kBitsPerColor);
net::WriteBigEndian<uint32>(header + kHeaderCupsBitsPerPixel, kBitsPerPixel);
net::WriteBigEndian<uint32>(header + kHeaderCupsBytesPerLine,
(kBitsPerPixel * image.size().width() + 7) / 8);
net::WriteBigEndian<uint32>(header + kHeaderCupsColorOrder, kColorOrder);
net::WriteBigEndian<uint32>(header + kHeaderCupsColorSpace, kColorSpace);
net::WriteBigEndian<uint32>(header + kHeaderCupsNumColors, kNumColors);
net::WriteBigEndian<uint32>(header + kHeaderPwgTotalPageCount, total_pages);
output->append(header, kHeaderSize);
}
bool PwgEncoder::EncodeRowFrom32Bit(const uint8* row, const int width,
const int color_space,
std::string* output) const {
void (*pixel_encoder)(const uint8*, std::string*);
switch (color_space) {
case BitmapImage::RGBA:
pixel_encoder = &encodePixelFromRGBA;
break;
case BitmapImage::BGRA:
pixel_encoder = &encodePixelFromBGRA;
break;
default:
LOG(ERROR) << "Unsupported colorspace.";
return false;
}
// Converts the list of uint8 to uint32 as every pixels contains 4 bytes
// of information and comparison of elements is easier. The actual management
// of the bytes of the pixel is done by template function P on the original
// array to avoid endian problems.
const uint32* pos = reinterpret_cast<const uint32*>(row);
const uint32* row_end = pos + width;
// According to PWG-raster, a sequence of N identical pixels (up to 128)
// can be encoded by a byte N-1, followed by the information on
// that pixel. Any generic sequence of N pixels (up to 128) can be encoded
// with (signed) byte 1-N, followed by the information on the N pixels.
// Notice that for sequences of 1 pixel there is no difference between
// the two encodings.
// It is usually better to encode every largest sequence of > 2 identical
// pixels together because it saves the most space. Every other pixel should
// be encoded in the smallest number of generic sequences.
while (pos != row_end) {
const uint32* it = pos + 1;
const uint32* end = std::min(pos + kPwgMaxPackedPixels, row_end);
// Counts how many identical pixels (up to 128).
while (it != end && *pos == *it) {
it++;
}
if (it != pos + 1) { // More than one pixel
output->push_back(static_cast<char>((it - pos) - 1));
pixel_encoder(reinterpret_cast<const uint8*>(pos), output);
pos = it;
} else {
// Finds how many pixels each different from the previous one (up to 128).
while (it != end && *it != *(it - 1)) {
it++;
}
// Optimization: ignores the last pixel of the sequence if it is followed
// by an identical pixel, as it is more convenient for it to be the start
// of a new sequence of identical pixels. Notice that we don't compare
// to end, but row_end.
if (it != row_end && *it == *(it - 1)) {
it--;
}
output->push_back(static_cast<char>(1 - (it - pos)));
while (pos != it) {
pixel_encoder(reinterpret_cast<const uint8*>(pos++), output);
}
}
}
return true;
}
inline const uint8* PwgEncoder::GetRow(const BitmapImage& image,
int row) const {
return image.pixel_data() + row * image.size().width() * image.channels();
}
// Given a pointer to a struct Image, create a PWG of the image and
// put the compressed image data in the std::string. Returns true on success.
// The content of the std::string is undefined on failure.
bool PwgEncoder::EncodePage(const BitmapImage& image,
const uint32 dpi,
const uint32 total_pages,
std::string* output) const {
// For now only some 4-channel colorspaces are supported.
if (image.channels() != 4) {
LOG(ERROR) << "Unsupported colorspace.";
return false;
}
EncodePageHeader(image, dpi, total_pages, output);
int row_size = image.size().width() * image.channels();
int row_number = 0;
while (row_number < image.size().height()) {
const uint8* current_row = GetRow(image, row_number++);
int num_identical_rows = 1;
// We count how many times the current row is repeated.
while (num_identical_rows < kPwgMaxPackedRows
&& row_number < image.size().height()
&& !memcmp(current_row, GetRow(image, row_number), row_size)) {
num_identical_rows++;
row_number++;
}
output->push_back(static_cast<char>(num_identical_rows - 1));
// Both supported colorspaces have a 32-bit pixels information.
if (!EncodeRowFrom32Bit(
current_row, image.size().width(), image.colorspace(), output)) {
return false;
}
}
return true;
}
} // namespace cloud_print