From eff8181a629035377ad4e74c24aa250d4c095a90 Mon Sep 17 00:00:00 2001 From: Ben Wagner Date: Fri, 1 Dec 2023 16:40:20 -0500 Subject: [pdf] Emit color space of images This also makes Skia PDF directly depend on the Skia JPEG encoders and decoders instead of making them build time optional. All known users of PDF already depend on these so it should not make a difference in practice. If there is a need to allow other (or no) encoders and decoders that could be considered as a follow up. Bug: chromium:999986,chromium:1465627 Change-Id: I4992b0d9267a30154cd176528fcb830a39a12494 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/784553 Reviewed-by: Brian Osman Commit-Queue: Ben Wagner --- BUILD.gn | 22 ++---- gn/pdf.gni | 1 - include/codec/SkCodec.h | 1 + include/private/SkEncodedInfo.h | 5 ++ public.bzl | 2 - relnotes/PDF_jpeg.md | 4 ++ src/codec/SkJpegCodec.cpp | 9 --- src/codec/SkJpegPriv.h | 4 -- src/pdf/BUILD.bazel | 14 ++-- src/pdf/SkJpegInfo.h | 18 ----- src/pdf/SkJpegInfo_libjpegturbo.cpp | 59 --------------- src/pdf/SkJpegInfo_none.cpp | 120 ------------------------------- src/pdf/SkPDFBitmap.cpp | 139 ++++++++++++++++++++++++++---------- src/pdf/SkPDFBitmap.h | 24 +++++++ src/pdf/SkPDFDocumentPriv.h | 4 ++ src/pdf/SkPDFTypes.cpp | 4 ++ src/pdf/SkPDFTypes.h | 1 + tests/PDFJpegEmbedTest.cpp | 37 ++++++---- 18 files changed, 183 insertions(+), 285 deletions(-) create mode 100644 relnotes/PDF_jpeg.md diff --git a/BUILD.gn b/BUILD.gn index aeec2d9460..e93503ee48 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1233,7 +1233,8 @@ optional("graphite") { } optional("pdf") { - enabled = skia_use_zlib && skia_enable_pdf + enabled = skia_use_zlib && skia_enable_pdf && skia_use_libjpeg_turbo_decode && + skia_use_libjpeg_turbo_encode public_defines = [ "SK_SUPPORT_PDF" ] deps = [ "//third_party/zlib" ] @@ -1248,21 +1249,10 @@ optional("pdf") { defines = [ "SK_PDF_USE_SFNTLY" ] } - if (skia_use_libjpeg_turbo_decode) { - deps += [ - ":jpeg_decode", - - # This is not a public_dep so we need to directly depend on it - # to use jpeg headers - "//third_party/libjpeg-turbo:libjpeg", - ] - sources += [ "src/pdf/SkJpegInfo_libjpegturbo.cpp" ] - } else { - sources += [ "src/pdf/SkJpegInfo_none.cpp" ] - } - if (skia_use_libjpeg_turbo_encode) { - deps += [ ":jpeg_encode" ] - } + deps += [ + ":jpeg_decode", + ":jpeg_encode", + ] } optional("xps") { diff --git a/gn/pdf.gni b/gn/pdf.gni index 4228265c9f..a51ad5f1a3 100644 --- a/gn/pdf.gni +++ b/gn/pdf.gni @@ -22,7 +22,6 @@ skia_pdf_sources = [ "$_src/pdf/SkClusterator.h", "$_src/pdf/SkDeflate.cpp", "$_src/pdf/SkDeflate.h", - "$_src/pdf/SkJpegInfo.h", "$_src/pdf/SkKeyedImage.cpp", "$_src/pdf/SkKeyedImage.h", "$_src/pdf/SkPDFBitmap.cpp", diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h index 70c1ec7c51..2f5f124c9b 100644 --- a/include/codec/SkCodec.h +++ b/include/codec/SkCodec.h @@ -1029,6 +1029,7 @@ private: friend class SkSampledCodec; friend class SkIcoCodec; friend class SkAndroidCodec; // for fEncodedInfo + friend class SkPDFBitmap; // for fEncodedInfo }; namespace SkCodecs { diff --git a/include/private/SkEncodedInfo.h b/include/private/SkEncodedInfo.h index 74e2ad1480..eb1f3257f2 100644 --- a/include/private/SkEncodedInfo.h +++ b/include/private/SkEncodedInfo.h @@ -29,6 +29,7 @@ public: static std::unique_ptr Make(const skcms_ICCProfile&); const skcms_ICCProfile* profile() const { return &fProfile; } + sk_sp data() const { return fData; } private: ICCProfile(const skcms_ICCProfile&, sk_sp = nullptr); @@ -197,6 +198,10 @@ public: if (!fProfile) return nullptr; return fProfile->profile(); } + sk_sp profileData() const { + if (!fProfile) return nullptr; + return fProfile->data(); + } uint8_t bitsPerComponent() const { return fBitsPerComponent; } diff --git a/public.bzl b/public.bzl index ebf0f4f774..8c44cb46e5 100644 --- a/public.bzl +++ b/public.bzl @@ -1363,8 +1363,6 @@ BASE_SRCS_ALL = [ "src/pdf/SkClusterator.h", "src/pdf/SkDeflate.cpp", "src/pdf/SkDeflate.h", - "src/pdf/SkJpegInfo.h", - "src/pdf/SkJpegInfo_none.cpp", "src/pdf/SkKeyedImage.cpp", "src/pdf/SkKeyedImage.h", "src/pdf/SkPDFBitmap.cpp", diff --git a/relnotes/PDF_jpeg.md b/relnotes/PDF_jpeg.md new file mode 100644 index 0000000000..15594bb32b --- /dev/null +++ b/relnotes/PDF_jpeg.md @@ -0,0 +1,4 @@ +The PDF code now directly depends on Skia's JPEG decoder and encoder. The build +time shims to avoid using a JPEG decoder and encoder have been removed. In the +future these may be made optional again by allowing the user to supply them at +runtime. diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp index b6848fff8d..33d0a58ee1 100644 --- a/src/codec/SkJpegCodec.cpp +++ b/src/codec/SkJpegCodec.cpp @@ -1412,12 +1412,3 @@ std::unique_ptr Decode(sk_sp data, } } // namespace SkJpegDecoder - -namespace SkJpegPriv { - -SkEncodedOrigin get_exif_orientation(jpeg_decompress_struct* dinfo) { - auto metadataDecoder = SkJpegMetadataDecoder::Make(get_sk_marker_list(dinfo)); - return get_exif_orientation(metadataDecoder->getExifMetadata(/*copyData=*/false)); -} - -} // namespace SkJpegPriv diff --git a/src/codec/SkJpegPriv.h b/src/codec/SkJpegPriv.h index 0e8441d3f2..5728b0237f 100644 --- a/src/codec/SkJpegPriv.h +++ b/src/codec/SkJpegPriv.h @@ -56,8 +56,4 @@ struct skjpeg_error_mgr : jpeg_error_mgr { jmp_buf* fStack[4] = {}; }; -namespace SkJpegPriv { -SkEncodedOrigin get_exif_orientation(jpeg_decompress_struct* dinfo); -} - #endif diff --git a/src/pdf/BUILD.bazel b/src/pdf/BUILD.bazel index b41f5aac18..38f6f081cb 100644 --- a/src/pdf/BUILD.bazel +++ b/src/pdf/BUILD.bazel @@ -38,7 +38,6 @@ PDF_FILES = [ "SkPDFMetadata.h", "SkPDFResourceDict.cpp", "SkPDFResourceDict.h", - "SkJpegInfo.h", "SkPDFShader.cpp", "SkPDFShader.h", "SkPDFSubsetFont.cpp", @@ -60,11 +59,13 @@ split_srcs_and_hdrs( files = PDF_FILES, ) +#TODO: remove after Chromium no longer references this. skia_filegroup( name = "jpeg_info_libjpeg", srcs = ["SkJpegInfo_libjpegturbo.cpp"], ) +#TODO: remove after Chromium no longer references this. skia_filegroup( name = "jpeg_info_none", srcs = ["SkJpegInfo_none.cpp"], @@ -74,10 +75,7 @@ skia_filegroup( name = "srcs", srcs = [ ":pdf_srcs", - ] + select({ - "//src/codec:jpeg_decode_codec": [":jpeg_info_libjpeg"], - "//conditions:default": [":jpeg_info_none"], - }), + ], visibility = ["//src:__pkg__"], ) @@ -113,7 +111,11 @@ selects.config_setting_group( skia_cc_deps( name = "deps", visibility = ["//src:__pkg__"], - deps = ["@zlib_skia//:zlib"] + select({ + deps = [ + "@zlib_skia//:zlib", + #"//src/codec:jpeg_decode", + #"//src/encode:jpeg_encode", + ] + select({ ":uses_harfbuzz": ["@harfbuzz"], "//conditions:default": [], }), diff --git a/src/pdf/SkJpegInfo.h b/src/pdf/SkJpegInfo.h index 736575d42f..17155e4830 100644 --- a/src/pdf/SkJpegInfo.h +++ b/src/pdf/SkJpegInfo.h @@ -7,22 +7,4 @@ #ifndef SkJpegInfo_DEFINED #define SkJpegInfo_DEFINED -#include "include/codec/SkEncodedOrigin.h" -#include "include/private/SkEncodedInfo.h" - -#include - -struct SkISize; - -/** Returns true if the data seems to be a valid JPEG image with a known colorType. - - @param [out] size Image size in pixels - @param [out] colorType Encoded color type (kGray_Color, kYUV_Color, several others). - @param [out] orientation EXIF Orientation of the image. -*/ -bool SkGetJpegInfo(const void* data, size_t len, - SkISize* size, - SkEncodedInfo::Color* colorType, - SkEncodedOrigin* orientation); - #endif // SkJpegInfo_DEFINED diff --git a/src/pdf/SkJpegInfo_libjpegturbo.cpp b/src/pdf/SkJpegInfo_libjpegturbo.cpp index 41e73757df..020c041a5a 100644 --- a/src/pdf/SkJpegInfo_libjpegturbo.cpp +++ b/src/pdf/SkJpegInfo_libjpegturbo.cpp @@ -7,62 +7,3 @@ * This file can parse information from a JPEG using libjpeg (which must be compiled in). */ -#include "src/pdf/SkJpegInfo.h" - -#include "include/codec/SkEncodedOrigin.h" -#include "include/core/SkSize.h" -#include "include/core/SkStream.h" -#include "include/core/SkTypes.h" -#include "include/private/SkEncodedInfo.h" -#include "include/private/base/SkTo.h" -#include "src/codec/SkJpegCodec.h" -#include "src/codec/SkJpegConstants.h" -#include "src/codec/SkJpegDecoderMgr.h" -#include "src/codec/SkJpegPriv.h" - -#include -#include - -extern "C" { - #include "jpeglib.h" // NO_G3_REWRITE -} - -bool SkGetJpegInfo(const void* data, size_t len, - SkISize* size, - SkEncodedInfo::Color* colorType, - SkEncodedOrigin* orientation) { - if (!SkJpegCodec::IsJpeg(data, len)) { - return false; - } - - SkMemoryStream stream(data, len); - JpegDecoderMgr decoderMgr(&stream); - // libjpeg errors will be caught and reported here - skjpeg_error_mgr::AutoPushJmpBuf jmp(decoderMgr.errorMgr()); - if (setjmp(jmp)) { - return false; - } - decoderMgr.init(); - jpeg_decompress_struct* dinfo = decoderMgr.dinfo(); - jpeg_save_markers(dinfo, kExifMarker, 0xFFFF); - jpeg_save_markers(dinfo, kICCMarker, 0xFFFF); - jpeg_save_markers(dinfo, kMpfMarker, 0xFFFF); - jpeg_save_markers(dinfo, kGainmapMarker, 0xFFFF); - if (JPEG_HEADER_OK != jpeg_read_header(dinfo, true)) { - return false; - } - SkEncodedInfo::Color encodedColorType; - if (!decoderMgr.getEncodedColor(&encodedColorType)) { - return false; // Unable to interpret the color channels as colors. - } - if (colorType) { - *colorType = encodedColorType; - } - if (orientation) { - *orientation = SkJpegPriv::get_exif_orientation(dinfo); - } - if (size) { - *size = {SkToS32(dinfo->image_width), SkToS32(dinfo->image_height)}; - } - return true; -} diff --git a/src/pdf/SkJpegInfo_none.cpp b/src/pdf/SkJpegInfo_none.cpp index b0e3cba263..78e44cd483 100644 --- a/src/pdf/SkJpegInfo_none.cpp +++ b/src/pdf/SkJpegInfo_none.cpp @@ -8,123 +8,3 @@ * it. This does not need Skia to be built with a JPEG library. */ -#include "src/pdf/SkJpegInfo.h" - -#include "include/codec/SkEncodedOrigin.h" -#include "include/core/SkSize.h" -#include "include/private/base/SkTo.h" - -namespace { -class JpegSegment { -public: - JpegSegment(const void* data, size_t size) - : fData(static_cast(data)) - , fSize(size) - , fOffset(0) - , fLength(0) {} - bool read() { - if (!this->readBigendianUint16(&fMarker)) { - return false; - } - if (JpegSegment::StandAloneMarker(fMarker)) { - fLength = 0; - fBuffer = nullptr; - return true; - } - if (!this->readBigendianUint16(&fLength) || fLength < 2) { - return false; - } - fLength -= 2; // Length includes itself for some reason. - if (fOffset + fLength > fSize) { - return false; // Segment too long. - } - fBuffer = &fData[fOffset]; - fOffset += fLength; - return true; - } - - bool isSOF() { - return (fMarker & 0xFFF0) == 0xFFC0 && fMarker != 0xFFC4 && - fMarker != 0xFFC8 && fMarker != 0xFFCC; - } - uint16_t marker() { return fMarker; } - uint16_t length() { return fLength; } - const char* data() { return fBuffer; } - - static uint16_t GetBigendianUint16(const char* ptr) { - // "the most significant byte shall come first" - return (static_cast(ptr[0]) << 8) | - static_cast(ptr[1]); - } - -private: - const char* const fData; - const size_t fSize; - size_t fOffset; - const char* fBuffer; - uint16_t fMarker; - uint16_t fLength; - - bool readBigendianUint16(uint16_t* value) { - if (fOffset + 2 > fSize) { - return false; - } - *value = JpegSegment::GetBigendianUint16(&fData[fOffset]); - fOffset += 2; - return true; - } - static bool StandAloneMarker(uint16_t marker) { - // RST[m] markers or SOI, EOI, TEM - return (marker & 0xFFF8) == 0xFFD0 || marker == 0xFFD8 || - marker == 0xFFD9 || marker == 0xFF01; - } -}; -} // namespace - -bool SkGetJpegInfo(const void* data, size_t len, - SkISize* size, - SkEncodedInfo::Color* colorType, - SkEncodedOrigin* orientation) { - static const uint16_t kSOI = 0xFFD8; - static const uint16_t kAPP0 = 0xFFE0; - JpegSegment segment(data, len); - if (!segment.read() || segment.marker() != kSOI) { - return false; // not a JPEG - } - if (!segment.read() || segment.marker() != kAPP0) { - return false; // not an APP0 segment - } - static const char kJfif[] = {'J', 'F', 'I', 'F', '\0'}; - SkASSERT(segment.data()); - if (SkToSizeT(segment.length()) < sizeof(kJfif) || - 0 != memcmp(segment.data(), kJfif, sizeof(kJfif))) { - return false; // Not JFIF JPEG - } - do { - if (!segment.read()) { - return false; // malformed JPEG - } - } while (!segment.isSOF()); - if (segment.length() < 6) { - return false; // SOF segment is short - } - if (8 != segment.data()[0]) { - return false; // Only support 8-bit precision - } - int numberOfComponents = segment.data()[5]; - if (numberOfComponents != 1 && numberOfComponents != 3) { - return false; // Invalid JFIF - } - if (size) { - *size = {JpegSegment::GetBigendianUint16(&segment.data()[3]), - JpegSegment::GetBigendianUint16(&segment.data()[1])}; - } - if (colorType) { - *colorType = numberOfComponents == 3 ? SkEncodedInfo::kYUV_Color - : SkEncodedInfo::kGray_Color; - } - if (orientation) { - *orientation = kTopLeft_SkEncodedOrigin; - } - return true; -} diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp index 8806150ce0..a3f4632e25 100644 --- a/src/pdf/SkPDFBitmap.cpp +++ b/src/pdf/SkPDFBitmap.cpp @@ -7,26 +7,34 @@ #include "src/pdf/SkPDFBitmap.h" +#include "include/codec/SkCodec.h" #include "include/codec/SkEncodedImageFormat.h" +#include "include/codec/SkJpegDecoder.h" #include "include/core/SkBitmap.h" #include "include/core/SkData.h" #include "include/core/SkExecutor.h" #include "include/core/SkImage.h" #include "include/core/SkStream.h" +#include "include/encode/SkICC.h" #include "include/encode/SkJpegEncoder.h" #include "include/private/SkColorData.h" #include "include/private/base/SkTo.h" #include "src/core/SkImageInfoPriv.h" #include "src/pdf/SkDeflate.h" -#include "src/pdf/SkJpegInfo.h" #include "src/pdf/SkPDFDocumentPriv.h" #include "src/pdf/SkPDFTypes.h" +#include "src/pdf/SkPDFUnion.h" #include "src/pdf/SkPDFUtils.h" -//////////////////////////////////////////////////////////////////////////////// + +/*static*/ const SkEncodedInfo& SkPDFBitmap::GetEncodedInfo(SkCodec& codec) { + return codec.getEncodedInfo(); +} + +namespace { // write a single byte to a stream n times. -static void fill_stream(SkWStream* out, char value, size_t n) { +void fill_stream(SkWStream* out, char value, size_t n) { char buffer[4096]; memset(buffer, value, sizeof(buffer)); for (size_t i = 0; i < n / sizeof(buffer); ++i) { @@ -44,7 +52,7 @@ static void fill_stream(SkWStream* out, char value, size_t n) { channel goes to black, and the should-be-transparent pixels are rendered as grey because of the separate soft mask and color resizing. e.g.: gm/bitmappremul.cpp */ -static SkColor get_neighbor_avg_color(const SkPixmap& bm, int xOrig, int yOrig) { +SkColor get_neighbor_avg_color(const SkPixmap& bm, int xOrig, int yOrig) { SkASSERT(kBGRA_8888_SkColorType == bm.colorType()); unsigned r = 0, g = 0, b = 0, n = 0; // Clamp the range to the edge of the bitmap. @@ -68,24 +76,22 @@ static SkColor get_neighbor_avg_color(const SkPixmap& bm, int xOrig, int yOrig) : SK_ColorTRANSPARENT; } -namespace { enum class SkPDFStreamFormat { DCT, Flate, Uncompressed }; -} template -static void emit_image_stream(SkPDFDocument* doc, - SkPDFIndirectReference ref, - T writeStream, - SkISize size, - const char* colorSpace, - SkPDFIndirectReference sMask, - int length, - SkPDFStreamFormat format) { +void emit_image_stream(SkPDFDocument* doc, + SkPDFIndirectReference ref, + T writeStream, + SkISize size, + SkPDFUnion&& colorSpace, + SkPDFIndirectReference sMask, + int length, + SkPDFStreamFormat format) { SkPDFDict pdfDict("XObject"); pdfDict.insertName("Subtype", "Image"); pdfDict.insertInt("Width", size.width()); pdfDict.insertInt("Height", size.height()); - pdfDict.insertName("ColorSpace", colorSpace); + pdfDict.insertUnion("ColorSpace", std::move(colorSpace)); if (sMask) { pdfDict.insertRef("SMask", sMask); } @@ -113,7 +119,7 @@ static void emit_image_stream(SkPDFDocument* doc, doc->emitStream(pdfDict, std::move(writeStream), ref); } -static void do_deflated_alpha(const SkPixmap& pm, SkPDFDocument* doc, SkPDFIndirectReference ref) { +void do_deflated_alpha(const SkPixmap& pm, SkPDFDocument* doc, SkPDFIndirectReference ref) { SkPDF::Metadata::CompressionLevel compressionLevel = doc->metadata().fCompressionLevel; SkPDFStreamFormat format = compressionLevel == SkPDF::Metadata::CompressionLevel::None ? SkPDFStreamFormat::Uncompressed @@ -156,14 +162,37 @@ static void do_deflated_alpha(const SkPixmap& pm, SkPDFDocument* doc, SkPDFIndir #endif int length = SkToInt(buffer.bytesWritten()); emit_image_stream(doc, ref, [&buffer](SkWStream* stream) { buffer.writeToAndReset(stream); }, - pm.info().dimensions(), "DeviceGray", SkPDFIndirectReference(), - length, format); + pm.info().dimensions(), SkPDFUnion::Name("DeviceGray"), + SkPDFIndirectReference(), length, format); } -static void do_deflated_image(const SkPixmap& pm, - SkPDFDocument* doc, - bool isOpaque, - SkPDFIndirectReference ref) { +SkPDFUnion write_icc_profile(SkPDFDocument* doc, sk_sp&& icc, int channels) { + SkPDFIndirectReference iccStreamRef; + { + static SkMutex iccProfileMapMutex; + SkAutoMutexExclusive lock(iccProfileMapMutex); + + SkPDFIndirectReference* ref = doc->fICCProfileMap.find(SkPDFIccProfileKey{icc}); + if (ref) { + iccStreamRef = *ref; + } else { + std::unique_ptr iccStreamDict = SkPDFMakeDict(); + iccStreamDict->insertInt("N", channels); + iccStreamRef = SkPDFStreamOut(std::move(iccStreamDict), SkMemoryStream::Make(icc), doc); + doc->fICCProfileMap.set(SkPDFIccProfileKey{icc}, iccStreamRef); + } + } + + std::unique_ptr iccPDF = SkPDFMakeArray(); + iccPDF->appendName("ICCBased"); + iccPDF->appendRef(iccStreamRef); + return SkPDFUnion::Object(std::move(iccPDF)); +} + +void do_deflated_image(const SkPixmap& pm, + SkPDFDocument* doc, + bool isOpaque, + SkPDFIndirectReference ref) { SkPDFIndirectReference sMask; if (!isOpaque) { sMask = doc->reserveRef(); @@ -179,18 +208,22 @@ static void do_deflated_image(const SkPixmap& pm, deflateWStream.emplace(&buffer, SkToInt(compressionLevel)); stream = &*deflateWStream; } - const char* colorSpace = "DeviceGray"; + SkPDFUnion colorSpace = SkPDFUnion::Name("DeviceGray"); + int channels; switch (pm.colorType()) { case kAlpha_8_SkColorType: + channels = 1; fill_stream(stream, '\x00', pm.width() * pm.height()); break; case kGray_8_SkColorType: + channels = 1; SkASSERT(sMask.fValue = -1); SkASSERT(pm.rowBytes() == (size_t)pm.width()); stream->write(pm.addr8(), pm.width() * pm.height()); break; default: - colorSpace = "DeviceRGB"; + colorSpace = SkPDFUnion::Name("DeviceRGB"); + channels = 3; SkASSERT(pm.alphaType() == kUnpremul_SkAlphaType); SkASSERT(pm.colorType() == kBGRA_8888_SkColorType); SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4); @@ -219,26 +252,40 @@ static void do_deflated_image(const SkPixmap& pm, if (deflateWStream) { deflateWStream->finalize(); } + + if (pm.colorSpace()) { + skcms_ICCProfile iccProfile; + pm.colorSpace()->toProfile(&iccProfile); + sk_sp iccData = SkWriteICCProfile(&iccProfile, ""); + colorSpace = write_icc_profile(doc, std::move(iccData), channels); + } + #ifdef SK_PDF_BASE85_BINARY SkPDFUtils::Base85Encode(buffer.detachAsStream(), &buffer); #endif int length = SkToInt(buffer.bytesWritten()); emit_image_stream(doc, ref, [&buffer](SkWStream* stream) { buffer.writeToAndReset(stream); }, - pm.info().dimensions(), colorSpace, sMask, length, format); + pm.info().dimensions(), std::move(colorSpace), sMask, length, format); if (!isOpaque) { do_deflated_alpha(pm, doc, sMask); } } -static bool do_jpeg(sk_sp data, SkPDFDocument* doc, SkISize size, - SkPDFIndirectReference ref) { - SkISize jpegSize; - SkEncodedInfo::Color jpegColorType; - SkEncodedOrigin exifOrientation; - if (!SkGetJpegInfo(data->data(), data->size(), &jpegSize, - &jpegColorType, &exifOrientation)) { +bool do_jpeg(sk_sp data, SkColorSpace* imageColorSpace, SkPDFDocument* doc, SkISize size, + SkPDFIndirectReference ref) { + static constexpr const SkCodecs::Decoder decoders[] = { + SkJpegDecoder::Decoder(), + }; + std::unique_ptr codec = SkCodec::MakeFromData(data, decoders); + if (!codec) { return false; } + + SkISize jpegSize = codec->dimensions(); + const SkEncodedInfo& encodedInfo = SkPDFBitmap::GetEncodedInfo(*codec); + SkEncodedInfo::Color jpegColorType = encodedInfo.color(); + SkEncodedOrigin exifOrientation = codec->getOrigin(); + bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color; bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color; if (jpegSize != size // Safety check. @@ -252,14 +299,28 @@ static bool do_jpeg(sk_sp data, SkPDFDocument* doc, SkISize size, data = buffer.detachAsData(); #endif + int channels = yuv ? 3 : 1; + SkPDFUnion colorSpace = yuv ? SkPDFUnion::Name("DeviceRGB") : SkPDFUnion::Name("DeviceGray"); + if (sk_sp encodedIccProfileData = encodedInfo.profileData()) { + colorSpace = write_icc_profile(doc, std::move(encodedIccProfileData), channels); + } else if (const skcms_ICCProfile* codecIccProfile = codec->getICCProfile()) { + sk_sp codecIccData = SkWriteICCProfile(codecIccProfile, ""); + colorSpace = write_icc_profile(doc, std::move(codecIccData), channels); + } else if (imageColorSpace) { + skcms_ICCProfile imageIccProfile; + imageColorSpace->toProfile(&imageIccProfile); + sk_sp imageIccData = SkWriteICCProfile(&imageIccProfile, ""); + colorSpace = write_icc_profile(doc, std::move(imageIccData), channels); + } + emit_image_stream(doc, ref, [&data](SkWStream* dst) { dst->write(data->data(), data->size()); }, - jpegSize, yuv ? "DeviceRGB" : "DeviceGray", + jpegSize, std::move(colorSpace), SkPDFIndirectReference(), SkToInt(data->size()), SkPDFStreamFormat::DCT); return true; } -static SkBitmap to_pixels(const SkImage* image) { +SkBitmap to_pixels(const SkImage* image) { SkBitmap bm; int w = image->width(), h = image->height(); @@ -273,7 +334,8 @@ static SkBitmap to_pixels(const SkImage* image) { default: { // TODO: makeColorSpace(sRGB) or actually tag the images SkAlphaType at = bm.isOpaque() ? kOpaque_SkAlphaType : kUnpremul_SkAlphaType; - bm.allocPixels(SkImageInfo::Make(w, h, kBGRA_8888_SkColorType, at)); + bm.allocPixels( + SkImageInfo::Make(w, h, kBGRA_8888_SkColorType, at, image->refColorSpace())); } } // TODO: support GPU images in PDFs @@ -291,8 +353,9 @@ void serialize_image(const SkImage* img, SkASSERT(doc); SkASSERT(encodingQuality >= 0); SkISize dimensions = img->dimensions(); + if (sk_sp data = img->refEncodedData()) { - if (do_jpeg(std::move(data), doc, dimensions, ref)) { + if (do_jpeg(std::move(data), img->colorSpace(), doc, dimensions, ref)) { return; } } @@ -304,7 +367,7 @@ void serialize_image(const SkImage* img, jOpts.fQuality = encodingQuality; SkDynamicMemoryWStream stream; if (SkJpegEncoder::Encode(&stream, pm, jOpts)) { - if (do_jpeg(stream.detachAsData(), doc, dimensions, ref)) { + if (do_jpeg(stream.detachAsData(), pm.colorSpace(), doc, dimensions, ref)) { return; } } @@ -312,6 +375,8 @@ void serialize_image(const SkImage* img, do_deflated_image(pm, doc, isOpaque, ref); } +} // namespace + SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img, SkPDFDocument* doc, int encodingQuality) { diff --git a/src/pdf/SkPDFBitmap.h b/src/pdf/SkPDFBitmap.h index bc2c57bd3b..d310d7b6a6 100644 --- a/src/pdf/SkPDFBitmap.h +++ b/src/pdf/SkPDFBitmap.h @@ -7,8 +7,13 @@ #ifndef SkPDFBitmap_DEFINED #define SkPDFBitmap_DEFINED +#include "include/core/SkData.h" +#include "src/core/SkChecksum.h" + +class SkCodec; class SkImage; class SkPDFDocument; +struct SkEncodedInfo; struct SkPDFIndirectReference; /** @@ -19,4 +24,23 @@ SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img, SkPDFDocument* doc, int encodingQuality = 101); +class SkPDFBitmap { +public: + static const SkEncodedInfo& GetEncodedInfo(SkCodec&); +}; + +struct SkPDFIccProfileKey { + sk_sp fData; + bool operator==(const SkPDFIccProfileKey& that) const { + return fData->equals(that.fData.get()); + } + bool operator!=(const SkPDFIccProfileKey& rhs) const { return !(*this == rhs); } + + struct Hash { + uint32_t operator()(const SkPDFIccProfileKey& k) const { + return SkChecksum::Hash32(k.fData->data(), k.fData->size()); + } + }; +}; + #endif // SkPDFBitmap_DEFINED diff --git a/src/pdf/SkPDFDocumentPriv.h b/src/pdf/SkPDFDocumentPriv.h index dffc239c3e..4d8e5eb892 100644 --- a/src/pdf/SkPDFDocumentPriv.h +++ b/src/pdf/SkPDFDocumentPriv.h @@ -12,6 +12,7 @@ #include "include/docs/SkPDFDocument.h" #include "include/private/base/SkMutex.h" #include "src/core/SkTHash.h" +#include "src/pdf/SkPDFBitmap.h" #include "src/pdf/SkPDFGraphicState.h" #include "src/pdf/SkPDFMetadata.h" #include "src/pdf/SkPDFShader.h" @@ -149,6 +150,9 @@ public: SkPDFIndirectReference, SkPDFGradientShader::KeyHash> fGradientPatternMap; skia_private::THashMap fPDFBitmapMap; + skia_private::THashMap fICCProfileMap; skia_private::THashMap> fTypefaceMetrics; skia_private::THashMap> fType1GlyphNames; skia_private::THashMap> fToUnicodeMap; diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp index 79c402a9ad..d9b6d854b8 100644 --- a/src/pdf/SkPDFTypes.cpp +++ b/src/pdf/SkPDFTypes.cpp @@ -527,6 +527,10 @@ void SkPDFDict::insertTextString(const char key[], SkString value) { fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::TextString(std::move(value))); } +void SkPDFDict::insertUnion(const char key[], SkPDFUnion&& value) { + fRecords.emplace_back(SkPDFUnion::Name(key), std::move(value)); +} + //////////////////////////////////////////////////////////////////////////////// diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h index 370c94878f..579434ba8e 100644 --- a/src/pdf/SkPDFTypes.h +++ b/src/pdf/SkPDFTypes.h @@ -186,6 +186,7 @@ public: void insertTextString(const char key[], const char value[]); void insertByteString(const char key[], SkString value); void insertTextString(const char key[], SkString value); + void insertUnion(const char key[], SkPDFUnion&&); private: std::vector> fRecords; diff --git a/tests/PDFJpegEmbedTest.cpp b/tests/PDFJpegEmbedTest.cpp index 28d09d3b82..d2593f12eb 100644 --- a/tests/PDFJpegEmbedTest.cpp +++ b/tests/PDFJpegEmbedTest.cpp @@ -6,6 +6,7 @@ */ #include "include/codec/SkEncodedOrigin.h" +#include "include/codec/SkJpegDecoder.h" #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" #include "include/core/SkData.h" @@ -17,7 +18,7 @@ #include "include/core/SkTypes.h" #include "include/docs/SkPDFDocument.h" #include "include/private/SkEncodedInfo.h" -#include "src/pdf/SkJpegInfo.h" +#include "src/pdf/SkPDFBitmap.h" #include "tests/Test.h" #include "tools/Resources.h" @@ -121,19 +122,29 @@ struct SkJFIFInfo { } fType; }; bool SkIsJFIF(const SkData* data, SkJFIFInfo* info) { - SkISize jpegSize; - SkEncodedInfo::Color jpegColorType; - SkEncodedOrigin exifOrientation; - if (data && SkGetJpegInfo(data->data(), data->size(), &jpegSize, - &jpegColorType, &exifOrientation)) { - bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color; - bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color; - if (goodColorType && kTopLeft_SkEncodedOrigin == exifOrientation) { - if (info) { - *info = {jpegSize, yuv ? SkJFIFInfo::kYCbCr : SkJFIFInfo::kGrayscale}; - } - return true; + static constexpr const SkCodecs::Decoder decoders[] = { + SkJpegDecoder::Decoder(), + }; + + if (!data) { + return false; + } + std::unique_ptr codec = SkCodec::MakeFromData(sk_ref_sp(data), decoders); + if (!codec) { + return false; + } + + SkISize jpegSize = codec->dimensions(); + SkEncodedInfo::Color jpegColorType = SkPDFBitmap::GetEncodedInfo(*codec).color(); + SkEncodedOrigin exifOrientation = codec->getOrigin(); + + bool yuv = jpegColorType == SkEncodedInfo::kYUV_Color; + bool goodColorType = yuv || jpegColorType == SkEncodedInfo::kGray_Color; + if (goodColorType && kTopLeft_SkEncodedOrigin == exifOrientation) { + if (info) { + *info = {jpegSize, yuv ? SkJFIFInfo::kYCbCr : SkJFIFInfo::kGrayscale}; } + return true; } return false; } -- cgit v1.2.3