/*
* Copyright 2015 Google Inc .
*
* Use of this source code is governed by a BSD - style license that can be
* found in the LICENSE file .
*/
#include "src/pdf/SkPDFBitmap.h"
#include "include/codec/SkCodec.h"
#include "include/codec/SkEncodedOrigin.h"
#include "include/core/SkAlphaType.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkData.h"
#include "include/core/SkExecutor.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkSize.h"
#include "include/core/SkStream.h"
#include "include/docs/SkPDFDocument.h"
#include "include/encode/SkICC.h"
#include "include/private/SkEncodedInfo.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkMutex.h"
#include "include/private/base/SkTo.h"
#include "modules/skcms/skcms.h"
#include "src/codec/SkCodecPriv.h"
#include "src/core/SkColorPriv.h"
#include "src/core/SkTHash.h"
#include "src/pdf/SkDeflate.h"
#include "src/pdf/SkPDFDocumentPriv.h"
#include "src/pdf/SkPDFTypes.h"
#include "src/pdf/SkPDFUnion.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <functional>
#include <memory>
#include <optional>
#include <utility>
namespace {
// write a single byte to a stream n times.
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) {
out->write(buffer, sizeof (buffer));
}
out->write(buffer, n % sizeof (buffer));
}
/* It is necessary to average the color component of transparent
pixels with their surrounding neighbors since the PDF renderer may
separately re - sample the alpha and color channels when the image is
not displayed at its native resolution . Since an alpha of zero
gives no information about the color component , the pathological
case is a white image with sharp transparency bounds - the color
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 */
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.
int ymin = std::max(0 , yOrig - 1 );
int ymax = std::min(yOrig + 1 , bm.height() - 1 );
int xmin = std::max(0 , xOrig - 1 );
int xmax = std::min(xOrig + 1 , bm.width() - 1 );
for (int y = ymin; y <= ymax; ++y) {
const SkColor* scanline = bm.addr32(0 , y);
for (int x = xmin; x <= xmax; ++x) {
SkColor color = scanline[x];
if (color != SK_ColorTRANSPARENT) {
r += SkColorGetR(color);
g += SkColorGetG(color);
b += SkColorGetB(color);
n++;
}
}
}
return n > 0 ? SkColorSetRGB(SkToU8(r / n), SkToU8(g / n), SkToU8(b / n))
: SK_ColorTRANSPARENT;
}
enum class SkPDFStreamFormat { DCT, Flate, Uncompressed };
template <typename T>
void emit_image_stream(SkPDFDocument* doc,
SkPDFIndirectReference ref,
T writeStream,
SkISize size,
SkPDFUnion&& colorSpace,
SkPDFIndirectReference sMask,
size_t length,
SkPDFStreamFormat format) {
if (!ref) {
return ;
}
SkPDFDict pdfDict("XObject" );
pdfDict.insertName("Subtype" , "Image" );
pdfDict.insertInt("Width" , size.width());
pdfDict.insertInt("Height" , size.height());
pdfDict.insertUnion("ColorSpace" , std::move(colorSpace));
if (sMask) {
pdfDict.insertRef("SMask" , sMask);
}
pdfDict.insertInt("BitsPerComponent" , 8 );
switch (format) {
case SkPDFStreamFormat::DCT: pdfDict.insertName("Filter" , "DCTDecode" ); break ;
case SkPDFStreamFormat::Flate: pdfDict.insertName("Filter" , "FlateDecode" ); break ;
case SkPDFStreamFormat::Uncompressed: break ;
}
if (format == SkPDFStreamFormat::DCT) {
pdfDict.insertInt("ColorTransform" , 0 );
}
pdfDict.insertInt("Length" , length);
doc->emitStream(pdfDict, std::move(writeStream), ref);
}
size_t 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
: SkPDFStreamFormat::Flate;
SkDynamicMemoryWStream buffer;
SkWStream* stream = &buffer;
std::optional<SkDeflateWStream> deflateWStream;
if (format == SkPDFStreamFormat::Flate) {
deflateWStream.emplace(&buffer, SkToInt(compressionLevel));
stream = &*deflateWStream;
}
if (kAlpha_8_SkColorType == pm.colorType()) {
SkASSERT(pm.rowBytes() == (size_t)pm.width());
stream->write(pm.addr8(), pm.width() * pm.height());
} else {
SkASSERT(pm.alphaType() == kUnpremul_SkAlphaType);
SkASSERT(pm.colorType() == kBGRA_8888_SkColorType);
SkASSERT(pm.rowBytes() == (size_t)pm.width() * 4 );
const uint32_t* ptr = pm.addr32();
const uint32_t* stop = ptr + pm.height() * pm.width();
uint8_t byteBuffer[4092 ];
uint8_t* bufferStop = byteBuffer + std::size(byteBuffer);
uint8_t* dst = byteBuffer;
while (ptr != stop) {
*dst++ = 0 xFF & ((*ptr++) >> SK_BGRA_A32_SHIFT);
if (dst == bufferStop) {
stream->write(byteBuffer, sizeof (byteBuffer));
dst = byteBuffer;
}
}
stream->write(byteBuffer, dst - byteBuffer);
}
if (deflateWStream) {
deflateWStream->finalize();
}
size_t length = SkToInt(buffer.bytesWritten());
emit_image_stream(doc, ref, [&buffer](SkWStream* stream) { buffer.writeToAndReset(stream); },
pm.info().dimensions(), SkPDFUnion::Name("DeviceGray" ),
SkPDFIndirectReference(), length, format);
return length;
}
SkPDFUnion write_icc_profile(SkPDFDocument* doc, sk_sp<const SkData>&& icc, int channels) {
SkPDFIndirectReference iccStreamRef;
{
static SkMutex iccProfileMapMutex;
SkAutoMutexExclusive lock(iccProfileMapMutex);
SkPDFIndirectReference* ref = doc->fICCProfileMap.find(SkPDFIccProfileKey{icc, channels});
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, channels}, iccStreamRef);
}
}
std::unique_ptr<SkPDFArray> iccPDF = SkPDFMakeArray();
iccPDF->appendName("ICCBased" );
iccPDF->appendRef(iccStreamRef);
return SkPDFUnion::Object(std::move(iccPDF));
}
bool icc_channel_mismatch(const skcms_ICCProfile* iccProfile, int expectedChannels) {
int iccChannels = -1 ;
if (iccProfile) {
iccChannels = skcms_GetInputChannelCount(iccProfile);
}
return 0 < iccChannels && expectedChannels != iccChannels;
}
size_t do_deflated_image(const SkPixmap& pm,
SkPDFDocument* doc,
bool isOpaque,
SkPDFIndirectReference ref) {
SkPDF::Metadata::CompressionLevel compressionLevel = doc->metadata().fCompressionLevel;
SkPDFStreamFormat format = compressionLevel == SkPDF::Metadata::CompressionLevel::None
? SkPDFStreamFormat::Uncompressed
: SkPDFStreamFormat::Flate;
SkDynamicMemoryWStream dynamic;
SkNullWStream writeOnly;
SkWStream* buffer = ref ? static_cast<SkWStream*>(&dynamic) : &writeOnly;
SkWStream* stream = buffer;
std::optional<SkDeflateWStream> deflateWStream;
if (format == SkPDFStreamFormat::Flate) {
deflateWStream.emplace(buffer, SkToInt(compressionLevel));
stream = &*deflateWStream;
}
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(isOpaque);
SkASSERT(pm.rowBytes() == (size_t)pm.width());
stream->write(pm.addr8(), pm.width() * pm.height());
break ;
default :
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 );
uint8_t byteBuffer[3072 ];
static_assert(std::size(byteBuffer) % 3 == 0 , "" );
uint8_t* bufferStop = byteBuffer + std::size(byteBuffer);
uint8_t* dst = byteBuffer;
for (int y = 0 ; y < pm.height(); ++y) {
const SkColor* src = pm.addr32(0 , y);
for (int x = 0 ; x < pm.width(); ++x) {
SkColor color = *src++;
if (SkColorGetA(color) == SK_AlphaTRANSPARENT) {
color = get_neighbor_avg_color(pm, x, y);
}
*dst++ = SkColorGetR(color);
*dst++ = SkColorGetG(color);
*dst++ = SkColorGetB(color);
if (dst == bufferStop) {
stream->write(byteBuffer, sizeof (byteBuffer));
dst = byteBuffer;
}
}
}
stream->write(byteBuffer, dst - byteBuffer);
}
if (deflateWStream) {
deflateWStream->finalize();
}
if (pm.colorSpace()) {
skcms_ICCProfile iccProfile;
pm.colorSpace()->toProfile(&iccProfile);
if (!icc_channel_mismatch(&iccProfile, channels)) {
sk_sp<SkData> iccData = SkWriteICCProfile(&iccProfile, "" );
colorSpace = write_icc_profile(doc, std::move(iccData), channels);
}
}
size_t length = buffer->bytesWritten();
SkPDFIndirectReference sMask;
if (!isOpaque && ref) {
sMask = doc->reserveRef();
}
emit_image_stream(doc, ref, [&dynamic](SkWStream* stream) { dynamic.writeToAndReset(stream); },
pm.info().dimensions(), std::move(colorSpace), sMask, length, format);
if (!isOpaque) {
length += do_deflated_alpha(pm, doc, sMask);
}
return length;
}
size_t do_jpeg(sk_sp<const SkData> data,
SkColorSpace* imageColorSpace,
SkPDFDocument* doc,
SkISize size,
SkPDFIndirectReference ref) {
if (!ref) {
return data->size();
}
SkPDF::DecodeJpegCallback decodeJPEG = doc->metadata().jpegDecoder;
if (!decodeJPEG) {
return 0 ;
}
std::unique_ptr<SkCodec> codec = decodeJPEG(data);
if (!codec) {
return 0 ;
}
SkISize jpegSize = codec->dimensions();
const SkEncodedInfo& encodedInfo = SkCodecPriv::GetEncodedInfo(codec.get());
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.
|| !goodColorType
|| kTopLeft_SkEncodedOrigin != exifOrientation) {
return 0 ;
}
int channels = yuv ? 3 : 1 ;
SkPDFUnion colorSpace = yuv ? SkPDFUnion::Name("DeviceRGB" ) : SkPDFUnion::Name("DeviceGray" );
if (sk_sp<const SkData> encodedIccProfileData = encodedInfo.profileData();
encodedIccProfileData && !icc_channel_mismatch(encodedInfo.profile(), channels))
{
colorSpace = write_icc_profile(doc, std::move(encodedIccProfileData), channels);
} else if (const skcms_ICCProfile* codecIccProfile = codec->getICCProfile();
codecIccProfile && !icc_channel_mismatch(codecIccProfile, channels))
{
sk_sp<SkData> codecIccData = SkWriteICCProfile(codecIccProfile, "" );
colorSpace = write_icc_profile(doc, std::move(codecIccData), channels);
} else if (imageColorSpace) {
skcms_ICCProfile imageIccProfile;
imageColorSpace->toProfile(&imageIccProfile);
if (!icc_channel_mismatch(&imageIccProfile, channels)) {
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, std::move(colorSpace),
SkPDFIndirectReference(), SkToInt(data->size()), SkPDFStreamFormat::DCT);
return data->size();
}
SkBitmap to_pixels(const SkImage* image) {
SkBitmap bm;
int w = image->width(),
h = image->height();
switch (image->colorType()) {
case kAlpha_8_SkColorType:
bm.allocPixels(SkImageInfo::MakeA8(w, h));
break ;
case kGray_8_SkColorType:
bm.allocPixels(SkImageInfo::Make(w, h, kGray_8_SkColorType, kOpaque_SkAlphaType));
break ;
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, image->refColorSpace()));
}
}
// TODO: support GPU images in PDFs
if (!image->readPixels(nullptr, bm.pixmap(), 0 , 0 )) {
bm.eraseColor(SkColorSetARGB(0 xFF, 0 , 0 , 0 ));
}
return bm;
}
size_t serialize_image(const SkImage* img,
int encodingQuality,
SkPDFDocument* doc,
SkPDFIndirectReference ref) {
SkASSERT(img);
SkASSERT(doc);
SkASSERT(encodingQuality >= 0 );
SkISize dimensions = img->dimensions();
if (sk_sp<const SkData> data = img->refEncodedData()) {
if (size_t size = do_jpeg(std::move(data), img->colorSpace(), doc, dimensions, ref)) {
return size;
}
}
SkBitmap bm = to_pixels(img);
const SkPixmap& pm = bm.pixmap();
SkPDF::EncodeJpegCallback encodeJPEG = doc->metadata().jpegEncoder;
bool isOpaque = pm.isOpaque() || pm.computeIsOpaque();
if (encodeJPEG && encodingQuality <= 100 && isOpaque) {
SkDynamicMemoryWStream stream;
if (encodeJPEG(&stream, pm, encodingQuality)) {
if (size_t size = do_jpeg(stream.detachAsData(), pm.colorSpace(), doc, dimensions, ref)) {
return size;
}
}
}
return do_deflated_image(pm, doc, isOpaque, ref);
}
} // namespace
size_t SkPDFSerializeImageSize(const SkImage* img, SkPDFDocument* doc, int encodingQuality) {
return serialize_image(img, encodingQuality, doc, SkPDFIndirectReference());
}
SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
SkPDFDocument* doc,
int encodingQuality) {
SkASSERT(img);
SkASSERT(doc);
SkPDFIndirectReference ref = doc->reserveRef();
if (SkExecutor* executor = doc->executor()) {
SkRef(img);
doc->incrementJobCount();
executor->add([img, encodingQuality, doc, ref]() {
serialize_image(img, encodingQuality, doc, ref);
SkSafeUnref(img);
doc->signalJobComplete();
});
return ref;
}
serialize_image(img, encodingQuality, doc, ref);
return ref;
}
Messung V0.5 in Prozent C=98 H=94 G=95
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-08-26)
¤
*© Formatika GbR, Deutschland