[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 <brianosman@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index aeec2d9..e93503e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1233,7 +1233,8 @@
 }
 
 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 @@
     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 4228265..a51ad5f 100644
--- a/gn/pdf.gni
+++ b/gn/pdf.gni
@@ -22,7 +22,6 @@
   "$_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 70c1ec7..2f5f124 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -1029,6 +1029,7 @@
     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 74e2ad1..eb1f325 100644
--- a/include/private/SkEncodedInfo.h
+++ b/include/private/SkEncodedInfo.h
@@ -29,6 +29,7 @@
         static std::unique_ptr<ICCProfile> Make(const skcms_ICCProfile&);
 
         const skcms_ICCProfile* profile() const { return &fProfile; }
+        sk_sp<SkData> data() const { return fData; }
     private:
         ICCProfile(const skcms_ICCProfile&, sk_sp<SkData> = nullptr);
 
@@ -197,6 +198,10 @@
         if (!fProfile) return nullptr;
         return fProfile->profile();
     }
+    sk_sp<SkData> profileData() const {
+        if (!fProfile) return nullptr;
+        return fProfile->data();
+    }
 
     uint8_t bitsPerComponent() const { return fBitsPerComponent; }
 
diff --git a/public.bzl b/public.bzl
index ebf0f4f..8c44cb4 100644
--- a/public.bzl
+++ b/public.bzl
@@ -1363,8 +1363,6 @@
     "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 0000000..15594bb
--- /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 b6848ff..33d0a58 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -1412,12 +1412,3 @@
 }
 
 }  // 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 0e8441d..5728b02 100644
--- a/src/codec/SkJpegPriv.h
+++ b/src/codec/SkJpegPriv.h
@@ -56,8 +56,4 @@
     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 b41f5aa..38f6f08 100644
--- a/src/pdf/BUILD.bazel
+++ b/src/pdf/BUILD.bazel
@@ -38,7 +38,6 @@
     "SkPDFMetadata.h",
     "SkPDFResourceDict.cpp",
     "SkPDFResourceDict.h",
-    "SkJpegInfo.h",
     "SkPDFShader.cpp",
     "SkPDFShader.h",
     "SkPDFSubsetFont.cpp",
@@ -60,11 +59,13 @@
     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 @@
     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 @@
 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 736575d..17155e4 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 <cstddef>
-
-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 41e7375..020c041 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 <cstddef>
-#include <csetjmp>
-
-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 b0e3cba..78e44cd4 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<const char*>(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<uint8_t>(ptr[0]) << 8) |
-            static_cast<uint8_t>(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 8806150..a3f4632 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 @@
    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 @@
                  : SK_ColorTRANSPARENT;
 }
 
-namespace {
 enum class SkPDFStreamFormat { DCT, Flate, Uncompressed };
-}
 
 template <typename T>
-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 @@
     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 @@
     #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<SkData>&& 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<SkPDFDict> iccStreamDict = SkPDFMakeDict();
+            iccStreamDict->insertInt("N", channels);
+            iccStreamRef = SkPDFStreamOut(std::move(iccStreamDict), SkMemoryStream::Make(icc), doc);
+            doc->fICCProfileMap.set(SkPDFIccProfileKey{icc}, iccStreamRef);
+        }
+    }
+
+    std::unique_ptr<SkPDFArray> 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 @@
         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 @@
     if (deflateWStream) {
         deflateWStream->finalize();
     }
+
+    if (pm.colorSpace()) {
+        skcms_ICCProfile iccProfile;
+        pm.colorSpace()->toProfile(&iccProfile);
+        sk_sp<SkData> 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<SkData> 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<SkData> data, SkColorSpace* imageColorSpace, SkPDFDocument* doc, SkISize size,
+             SkPDFIndirectReference ref) {
+    static constexpr const SkCodecs::Decoder decoders[] = {
+        SkJpegDecoder::Decoder(),
+    };
+    std::unique_ptr<SkCodec> 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 @@
     data = buffer.detachAsData();
     #endif
 
+    int channels = yuv ? 3 : 1;
+    SkPDFUnion colorSpace = yuv ? SkPDFUnion::Name("DeviceRGB") : SkPDFUnion::Name("DeviceGray");
+    if (sk_sp<SkData> encodedIccProfileData = encodedInfo.profileData()) {
+        colorSpace = write_icc_profile(doc, std::move(encodedIccProfileData), channels);
+    } else if (const skcms_ICCProfile* codecIccProfile = codec->getICCProfile()) {
+        sk_sp<SkData> codecIccData = SkWriteICCProfile(codecIccProfile, "");
+        colorSpace = write_icc_profile(doc, std::move(codecIccData), channels);
+    } else if (imageColorSpace) {
+        skcms_ICCProfile imageIccProfile;
+        imageColorSpace->toProfile(&imageIccProfile);
+        sk_sp<SkData> 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 @@
         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 @@
     SkASSERT(doc);
     SkASSERT(encodingQuality >= 0);
     SkISize dimensions = img->dimensions();
+
     if (sk_sp<SkData> 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 @@
         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 @@
     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 bc2c57b..d310d7b 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 @@
                                            SkPDFDocument* doc,
                                            int encodingQuality = 101);
 
+class SkPDFBitmap {
+public:
+    static const SkEncodedInfo& GetEncodedInfo(SkCodec&);
+};
+
+struct SkPDFIccProfileKey {
+    sk_sp<SkData> 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 dffc239..4d8e5eb 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 @@
                            SkPDFIndirectReference,
                            SkPDFGradientShader::KeyHash> fGradientPatternMap;
     skia_private::THashMap<SkBitmapKey, SkPDFIndirectReference> fPDFBitmapMap;
+    skia_private::THashMap<SkPDFIccProfileKey,
+                           SkPDFIndirectReference,
+                           SkPDFIccProfileKey::Hash> fICCProfileMap;
     skia_private::THashMap<uint32_t, std::unique_ptr<SkAdvancedTypefaceMetrics>> fTypefaceMetrics;
     skia_private::THashMap<uint32_t, std::vector<SkString>> fType1GlyphNames;
     skia_private::THashMap<uint32_t, std::vector<SkUnichar>> fToUnicodeMap;
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index 79c402a..d9b6d85 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -527,6 +527,10 @@
     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 370c948..579434b 100644
--- a/src/pdf/SkPDFTypes.h
+++ b/src/pdf/SkPDFTypes.h
@@ -186,6 +186,7 @@
     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<std::pair<SkPDFUnion, SkPDFUnion>> fRecords;
diff --git a/tests/PDFJpegEmbedTest.cpp b/tests/PDFJpegEmbedTest.cpp
index 28d09d3..d2593f1 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 @@
     } 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<SkCodec> 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;
 }