| // 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 |