// 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 "components/pwg_encoder/pwg_encoder.h" #include #include #include #include #include "base/big_endian.h" #include "base/logging.h" #include "components/pwg_encoder/bitmap_image.h" namespace pwg_encoder { namespace { const uint32_t kBitsPerColor = 8; const uint32_t kColorOrder = 0; // chunky. // Coefficients used to convert from RGB to monochrome. const uint32_t kRedCoefficient = 2125; const uint32_t kGreenCoefficient = 7154; const uint32_t kBlueCoefficient = 721; const uint32_t kColorCoefficientDenominator = 10000; const char kPwgKeyword[] = "RaS2"; const uint32_t kHeaderSize = 1796; const uint32_t kHeaderCupsDuplex = 272; const uint32_t kHeaderCupsHwResolutionHorizontal = 276; const uint32_t kHeaderCupsHwResolutionVertical = 280; const uint32_t kHeaderCupsTumble = 368; const uint32_t kHeaderCupsWidth = 372; const uint32_t kHeaderCupsHeight = 376; const uint32_t kHeaderCupsBitsPerColor = 384; const uint32_t kHeaderCupsBitsPerPixel = 388; const uint32_t kHeaderCupsBytesPerLine = 392; const uint32_t kHeaderCupsColorOrder = 396; const uint32_t kHeaderCupsColorSpace = 400; const uint32_t kHeaderCupsNumColors = 420; const uint32_t kHeaderPwgTotalPageCount = 452; const uint32_t kHeaderPwgCrossFeedTransform = 456; const uint32_t kHeaderPwgFeedTransform = 460; const int kPwgMaxPackedRows = 256; const int kPwgMaxPackedPixels = 128; struct RGBA8 { uint8_t red; uint8_t green; uint8_t blue; uint8_t alpha; }; struct BGRA8 { uint8_t blue; uint8_t green; uint8_t red; uint8_t alpha; }; template void EncodePixelToRGB(const void* pixel, std::string* output) { const InputStruct* i = reinterpret_cast(pixel); output->push_back(static_cast(i->red)); output->push_back(static_cast(i->green)); output->push_back(static_cast(i->blue)); } template void EncodePixelToMonochrome(const void* pixel, std::string* output) { const InputStruct* i = reinterpret_cast(pixel); output->push_back(static_cast((i->red * kRedCoefficient + i->green * kGreenCoefficient + i->blue * kBlueCoefficient) / kColorCoefficientDenominator)); } std::string EncodePageHeader(const BitmapImage& image, const PwgHeaderInfo& pwg_header_info) { char header[kHeaderSize]; memset(header, 0, kHeaderSize); uint32_t num_colors = pwg_header_info.color_space == PwgHeaderInfo::SGRAY ? 1 : 3; uint32_t bits_per_pixel = num_colors * kBitsPerColor; base::WriteBigEndian(header + kHeaderCupsDuplex, pwg_header_info.duplex ? 1 : 0); base::WriteBigEndian(header + kHeaderCupsHwResolutionHorizontal, pwg_header_info.dpi.width()); base::WriteBigEndian(header + kHeaderCupsHwResolutionVertical, pwg_header_info.dpi.height()); base::WriteBigEndian(header + kHeaderCupsTumble, pwg_header_info.tumble ? 1 : 0); base::WriteBigEndian(header + kHeaderCupsWidth, image.size().width()); base::WriteBigEndian(header + kHeaderCupsHeight, image.size().height()); base::WriteBigEndian(header + kHeaderCupsBitsPerColor, kBitsPerColor); base::WriteBigEndian(header + kHeaderCupsBitsPerPixel, bits_per_pixel); base::WriteBigEndian( header + kHeaderCupsBytesPerLine, (bits_per_pixel * image.size().width() + 7) / 8); base::WriteBigEndian(header + kHeaderCupsColorOrder, kColorOrder); base::WriteBigEndian(header + kHeaderCupsColorSpace, pwg_header_info.color_space); base::WriteBigEndian(header + kHeaderCupsNumColors, num_colors); base::WriteBigEndian(header + kHeaderPwgCrossFeedTransform, pwg_header_info.flipx ? -1 : 1); base::WriteBigEndian(header + kHeaderPwgFeedTransform, pwg_header_info.flipy ? -1 : 1); base::WriteBigEndian(header + kHeaderPwgTotalPageCount, pwg_header_info.total_pages); return std::string(header, kHeaderSize); } template void EncodeRow(RandomAccessIterator pos, RandomAccessIterator row_end, bool monochrome, std::string* output) { // 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 129) 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. // We encode every largest sequence of identical pixels together because it // usually saves the most space. Every other pixel should be encoded in the // smallest number of generic sequences. // NOTE: the algorithm is not optimal especially in case of monochrome. while (pos != row_end) { RandomAccessIterator it = pos + 1; RandomAccessIterator 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((it - pos) - 1)); if (monochrome) EncodePixelToMonochrome(&*pos, output); else EncodePixelToRGB(&*pos, output); pos = it; } else { // Finds how many pixels there are each different from the previous one. // IMPORTANT: even if sequences of different pixels can contain as many // as 129 pixels, we restrict to 128 because some decoders don't manage // it correctly. So iterating until it != end is correct. 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(1 - (it - pos))); while (pos != it) { if (monochrome) EncodePixelToMonochrome(&*pos, output); else EncodePixelToRGB(&*pos, output); ++pos; } } } } const uint8_t* GetRow(const BitmapImage& image, int row, bool flipy) { return image.GetPixel( gfx::Point(0, flipy ? image.size().height() - 1 - row : row)); } template std::string EncodePageWithColorspace(const BitmapImage& image, const PwgHeaderInfo& pwg_header_info) { bool monochrome = pwg_header_info.color_space == PwgHeaderInfo::SGRAY; std::string output = EncodePageHeader(image, pwg_header_info); // Ensure no integer overflow. CHECK(image.size().width() < INT_MAX / image.channels()); int row_size = image.size().width() * image.channels(); int row_number = 0; while (row_number < image.size().height()) { const uint8_t* current_row = GetRow(image, row_number++, pwg_header_info.flipy); 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, pwg_header_info.flipy), row_size)) { num_identical_rows++; row_number++; } output.push_back(static_cast(num_identical_rows - 1)); // Both supported colorspaces have a 32-bit pixels information. // Converts the list of uint8_t to uint32_t 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 pixel_encoder function // on the original array to avoid endian problems. const uint32_t* pos = reinterpret_cast(current_row); const uint32_t* row_end = pos + image.size().width(); if (!pwg_header_info.flipx) { EncodeRow(pos, row_end, monochrome, &output); } else { // We reverse the iterators. EncodeRow(std::reverse_iterator(row_end), std::reverse_iterator(pos), monochrome, &output); } } return output; } } // namespace // static std::string PwgEncoder::GetDocumentHeader() { std::string output; output.append(kPwgKeyword, 4); return output; } // static std::string PwgEncoder::EncodePage(const BitmapImage& image, const PwgHeaderInfo& pwg_header_info) { // pwg_header_info.color_space can only contain color spaces that are // supported, so no sanity check is needed. std::string data; switch (image.colorspace()) { case BitmapImage::RGBA: data = EncodePageWithColorspace(image, pwg_header_info); break; case BitmapImage::BGRA: data = EncodePageWithColorspace(image, pwg_header_info); break; default: LOG(ERROR) << "Unsupported colorspace."; break; } return data; } } // namespace pwg_encoder