Spaces:
Running
Running
//======================================================================== | |
// | |
// ImageOutputDev.cc | |
// | |
// Copyright 1998-2003 Glyph & Cog, LLC | |
// | |
//======================================================================== | |
//======================================================================== | |
// | |
// Modified under the Poppler project - http://poppler.freedesktop.org | |
// | |
// All changes made under the Poppler project to this file are licensed | |
// under GPL version 2 or later | |
// | |
// Copyright (C) 2005, 2007, 2011, 2018, 2019, 2021, 2022 Albert Astals Cid <[email protected]> | |
// Copyright (C) 2006 Rainer Keller <[email protected]> | |
// Copyright (C) 2008 Timothy Lee <[email protected]> | |
// Copyright (C) 2008 Vasile Gaburici <[email protected]> | |
// Copyright (C) 2009 Carlos Garcia Campos <[email protected]> | |
// Copyright (C) 2009 William Bader <[email protected]> | |
// Copyright (C) 2010 Jakob Voss <[email protected]> | |
// Copyright (C) 2012, 2013, 2017, 2018 Adrian Johnson <[email protected]> | |
// Copyright (C) 2013 Thomas Fischer <[email protected]> | |
// Copyright (C) 2013 Hib Eris <[email protected]> | |
// Copyright (C) 2017 Caolán McNamara <[email protected]> | |
// Copyright (C) 2018 Andreas Gruenbacher <[email protected]> | |
// Copyright (C) 2020 mrbax <[email protected]> | |
// Copyright (C) 2024 Fernando Herrera <[email protected]> | |
// Copyright (C) 2024 Sebastian J. Bronner <[email protected]> | |
// | |
// To see a description of the changes please see the Changelog file that | |
// came with your tarball or type make ChangeLog if you are building from git | |
// | |
//======================================================================== | |
ImageOutputDev::ImageOutputDev(char *fileRootA, bool pageNamesA, bool listImagesA) | |
{ | |
listImages = listImagesA; | |
if (!listImages) { | |
fileRoot = copyString(fileRootA); | |
fileName = (char *)gmalloc(strlen(fileRoot) + 45); | |
} | |
outputPNG = false; | |
outputTiff = false; | |
dumpJPEG = false; | |
dumpJP2 = false; | |
dumpJBIG2 = false; | |
dumpCCITT = false; | |
pageNames = pageNamesA; | |
printFilenames = false; | |
imgNum = 0; | |
pageNum = 0; | |
errorCode = 0; | |
if (listImages) { | |
printf("page num type width height color comp bpc enc interp object ID x-ppi y-ppi size ratio\n"); | |
printf("--------------------------------------------------------------------------------------------\n"); | |
} | |
} | |
ImageOutputDev::~ImageOutputDev() | |
{ | |
if (!listImages) { | |
gfree(fileName); | |
gfree(fileRoot); | |
} | |
} | |
void ImageOutputDev::setFilename(const char *fileExt) | |
{ | |
if (pageNames) { | |
sprintf(fileName, "%s-%03d-%03d.%s", fileRoot, pageNum, imgNum, fileExt); | |
} else { | |
sprintf(fileName, "%s-%03d.%s", fileRoot, imgNum, fileExt); | |
} | |
} | |
// Print a floating point number between 0 - 9999 using 4 characters | |
// eg '1.23', '12.3', ' 123', '1234' | |
// | |
// We need to be careful to handle the cases where rounding adds an | |
// extra digit before the decimal. eg printf("%4.2f", 9.99999) | |
// outputs "10.00" instead of "9.99". | |
static void printNumber(double d) | |
{ | |
char buf[10]; | |
if (d < 10.0) { | |
sprintf(buf, "%4.2f", d); | |
buf[4] = 0; | |
printf("%s", buf); | |
} else if (d < 100.0) { | |
sprintf(buf, "%4.1f", d); | |
if (!isdigit(buf[3])) { | |
buf[3] = 0; | |
printf(" %s", buf); | |
} else { | |
printf("%s", buf); | |
} | |
} else { | |
printf("%4.0f", d); | |
} | |
} | |
void ImageOutputDev::listImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, bool inlineImg, ImageType imageType) | |
{ | |
const char *type; | |
const char *colorspace; | |
const char *enc; | |
int components, bpc; | |
printf("%4d %5d ", pageNum, imgNum); | |
type = ""; | |
switch (imageType) { | |
case imgImage: | |
type = "image"; | |
break; | |
case imgStencil: | |
type = "stencil"; | |
break; | |
case imgMask: | |
type = "mask"; | |
break; | |
case imgSmask: | |
type = "smask"; | |
break; | |
} | |
printf("%-7s %5d %5d ", type, width, height); | |
colorspace = "-"; | |
/* masks and stencils default to ncomps = 1 and bpc = 1 */ | |
components = 1; | |
bpc = 1; | |
if (colorMap && colorMap->isOk()) { | |
switch (colorMap->getColorSpace()->getMode()) { | |
case csDeviceGray: | |
case csCalGray: | |
colorspace = "gray"; | |
break; | |
case csDeviceRGB: | |
case csCalRGB: | |
colorspace = "rgb"; | |
break; | |
case csDeviceCMYK: | |
colorspace = "cmyk"; | |
break; | |
case csLab: | |
colorspace = "lab"; | |
break; | |
case csICCBased: | |
colorspace = "icc"; | |
break; | |
case csIndexed: | |
colorspace = "index"; | |
break; | |
case csSeparation: | |
colorspace = "sep"; | |
break; | |
case csDeviceN: | |
colorspace = "devn"; | |
break; | |
case csPattern: | |
default: | |
colorspace = "-"; | |
break; | |
} | |
components = colorMap->getNumPixelComps(); | |
bpc = colorMap->getBits(); | |
} | |
printf("%-5s %2d %2d ", colorspace, components, bpc); | |
switch (str->getKind()) { | |
case strCCITTFax: | |
enc = "ccitt"; | |
break; | |
case strDCT: | |
enc = "jpeg"; | |
break; | |
case strJPX: | |
enc = "jpx"; | |
break; | |
case strJBIG2: | |
enc = "jbig2"; | |
break; | |
case strFile: | |
case strFlate: | |
case strCachedFile: | |
case strASCIIHex: | |
case strASCII85: | |
case strLZW: | |
case strRunLength: | |
case strWeird: | |
default: | |
enc = "image"; | |
break; | |
} | |
printf("%-5s ", enc); | |
printf("%-3s ", interpolate ? "yes" : "no"); | |
if (inlineImg) { | |
printf("[inline] "); | |
} else if (ref->isRef()) { | |
const Ref imageRef = ref->getRef(); | |
if (imageRef.gen >= 100000) { | |
printf("[none] "); | |
} else { | |
printf(" %6d %2d ", imageRef.num, imageRef.gen); | |
} | |
} else { | |
printf("[none] "); | |
} | |
const double *mat = state->getCTM(); | |
double width2 = sqrt(mat[0] * mat[0] + mat[1] * mat[1]); | |
double height2 = sqrt(mat[2] * mat[2] + mat[3] * mat[3]); | |
double xppi = fabs(width * 72.0 / width2); | |
double yppi = fabs(height * 72.0 / height2); | |
if (xppi < 1.0) { | |
printf("%5.3f ", xppi); | |
} else { | |
printf("%5.0f ", xppi); | |
} | |
if (yppi < 1.0) { | |
printf("%5.3f ", yppi); | |
} else { | |
printf("%5.0f ", yppi); | |
} | |
Goffset embedSize = -1; | |
if (inlineImg) { | |
embedSize = getInlineImageLength(str, width, height, colorMap); | |
} else { | |
embedSize = str->getBaseStream()->getLength(); | |
} | |
long long imageSize = 0; | |
if (colorMap && colorMap->isOk()) { | |
imageSize = ((long long)width * height * colorMap->getNumPixelComps() * colorMap->getBits()) / 8; | |
} else { | |
imageSize = (long long)width * height / 8; // mask | |
} | |
double ratio = -1.0; | |
if (imageSize > 0) { | |
ratio = 100.0 * embedSize / imageSize; | |
} | |
if (embedSize < 0) { | |
printf(" - "); | |
} else if (embedSize <= 9999) { | |
printf("%4lldB", embedSize); | |
} else { | |
double d = embedSize / 1024.0; | |
if (d <= 9999.0) { | |
printNumber(d); | |
putchar('K'); | |
} else { | |
d /= 1024.0; | |
if (d <= 9999.0) { | |
printNumber(d); | |
putchar('M'); | |
} else { | |
d /= 1024.0; | |
printNumber(d); | |
putchar('G'); | |
} | |
} | |
} | |
if (ratio > 9.9) { | |
printf(" %3.0f%%\n", ratio); | |
} else if (ratio >= 0.0) { | |
printf(" %3.1f%%\n", ratio); | |
} else { | |
printf(" - \n"); | |
} | |
++imgNum; | |
} | |
long ImageOutputDev::getInlineImageLength(Stream *str, int width, int height, GfxImageColorMap *colorMap) | |
{ | |
long len; | |
if (colorMap) { | |
ImageStream *imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); | |
imgStr->reset(); | |
for (int y = 0; y < height; y++) { | |
imgStr->getLine(); | |
} | |
imgStr->close(); | |
delete imgStr; | |
} else { | |
str->reset(); | |
for (int y = 0; y < height; y++) { | |
int size = (width + 7) / 8; | |
for (int x = 0; x < size; x++) { | |
str->getChar(); | |
} | |
} | |
} | |
EmbedStream *embedStr = (EmbedStream *)(str->getBaseStream()); | |
embedStr->rewind(); | |
len = 0; | |
while (embedStr->getChar() != EOF) { | |
len++; | |
} | |
embedStr->restore(); | |
return len; | |
} | |
void ImageOutputDev::writeRawImage(Stream *str, const char *ext) | |
{ | |
FILE *f; | |
int c; | |
// open the image file | |
setFilename(ext); | |
++imgNum; | |
if (!(f = fopen(fileName, "wb"))) { | |
error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); | |
errorCode = 2; | |
return; | |
} | |
// initialize stream | |
str = str->getNextStream(); | |
str->reset(); | |
// copy the stream | |
while ((c = str->getChar()) != EOF) { | |
fputc(c, f); | |
} | |
str->close(); | |
fclose(f); | |
} | |
void ImageOutputDev::writeImageFile(ImgWriter *writer, ImageFormat format, const char *ext, Stream *str, int width, int height, GfxImageColorMap *colorMap) | |
{ | |
FILE *f = nullptr; /* squelch bogus compiler warning */ | |
ImageStream *imgStr = nullptr; | |
unsigned char *row; | |
unsigned char *rowp; | |
unsigned char *p; | |
GfxRGB rgb; | |
GfxCMYK cmyk; | |
GfxGray gray; | |
unsigned char zero[gfxColorMaxComps]; | |
int invert_bits; | |
if (writer) { | |
setFilename(ext); | |
++imgNum; | |
if (!(f = fopen(fileName, "wb"))) { | |
error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); | |
errorCode = 2; | |
return; | |
} | |
if (!writer->init(f, width, height, 72, 72)) { | |
error(errIO, -1, "Error writing '{0:s}'", fileName); | |
errorCode = 2; | |
return; | |
} | |
} | |
int pixelSize = sizeof(unsigned int); | |
if (format == imgRGB48) { | |
pixelSize = 2 * sizeof(unsigned int); | |
} | |
row = (unsigned char *)gmallocn_checkoverflow(width, pixelSize); | |
if (!row) { | |
error(errIO, -1, "Image data for '{0:s}' is too big. {1:d} width with {2:d} bytes per pixel", fileName, width, pixelSize); | |
errorCode = 99; | |
return; | |
} | |
if (format != imgMonochrome) { | |
// initialize stream | |
imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); | |
imgStr->reset(); | |
} else { | |
// initialize stream | |
str->reset(); | |
} | |
// PDF masks use 0 = draw current color, 1 = leave unchanged. | |
// We invert this to provide the standard interpretation of alpha | |
// (0 = transparent, 1 = opaque). If the colorMap already inverts | |
// the mask we leave the data unchanged. | |
invert_bits = 0xff; | |
if (colorMap) { | |
memset(zero, 0, sizeof(zero)); | |
colorMap->getGray(zero, &gray); | |
if (colToByte(gray) == 0) { | |
invert_bits = 0x00; | |
} | |
} | |
// for each line... | |
for (int y = 0; y < height; y++) { | |
switch (format) { | |
case imgRGB: | |
p = imgStr->getLine(); | |
rowp = row; | |
for (int x = 0; x < width; ++x) { | |
if (p) { | |
colorMap->getRGB(p, &rgb); | |
*rowp++ = colToByte(rgb.r); | |
*rowp++ = colToByte(rgb.g); | |
*rowp++ = colToByte(rgb.b); | |
p += colorMap->getNumPixelComps(); | |
} else { | |
*rowp++ = 0; | |
*rowp++ = 0; | |
*rowp++ = 0; | |
} | |
} | |
if (writer) { | |
writer->writeRow(&row); | |
} | |
break; | |
case imgRGB48: { | |
p = imgStr->getLine(); | |
unsigned short *rowp16 = reinterpret_cast<unsigned short *>(row); | |
for (int x = 0; x < width; ++x) { | |
if (p) { | |
colorMap->getRGB(p, &rgb); | |
*rowp16++ = colToShort(rgb.r); | |
*rowp16++ = colToShort(rgb.g); | |
*rowp16++ = colToShort(rgb.b); | |
p += colorMap->getNumPixelComps(); | |
} else { | |
*rowp16++ = 0; | |
*rowp16++ = 0; | |
*rowp16++ = 0; | |
} | |
} | |
if (writer) { | |
writer->writeRow(&row); | |
} | |
break; | |
} | |
case imgCMYK: | |
p = imgStr->getLine(); | |
rowp = row; | |
for (int x = 0; x < width; ++x) { | |
if (p) { | |
colorMap->getCMYK(p, &cmyk); | |
*rowp++ = colToByte(cmyk.c); | |
*rowp++ = colToByte(cmyk.m); | |
*rowp++ = colToByte(cmyk.y); | |
*rowp++ = colToByte(cmyk.k); | |
p += colorMap->getNumPixelComps(); | |
} else { | |
*rowp++ = 0; | |
*rowp++ = 0; | |
*rowp++ = 0; | |
*rowp++ = 0; | |
} | |
} | |
if (writer) { | |
writer->writeRow(&row); | |
} | |
break; | |
case imgGray: | |
p = imgStr->getLine(); | |
rowp = row; | |
for (int x = 0; x < width; ++x) { | |
if (p) { | |
colorMap->getGray(p, &gray); | |
*rowp++ = colToByte(gray); | |
p += colorMap->getNumPixelComps(); | |
} else { | |
*rowp++ = 0; | |
} | |
} | |
if (writer) { | |
writer->writeRow(&row); | |
} | |
break; | |
case imgMonochrome: | |
int size = (width + 7) / 8; | |
for (int x = 0; x < size; x++) { | |
row[x] = str->getChar() ^ invert_bits; | |
} | |
if (writer) { | |
writer->writeRow(&row); | |
} | |
break; | |
} | |
} | |
gfree(row); | |
if (format != imgMonochrome) { | |
imgStr->close(); | |
delete imgStr; | |
} | |
str->close(); | |
if (writer) { | |
writer->close(); | |
fclose(f); | |
} | |
} | |
void ImageOutputDev::writeImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool inlineImg) | |
{ | |
ImageFormat format; | |
EmbedStream *embedStr; | |
if (inlineImg) { | |
embedStr = (EmbedStream *)(str->getBaseStream()); | |
// Record the stream. This determines the size. | |
getInlineImageLength(str, width, height, colorMap); | |
// Reading the stream again will return EOF at end of recording. | |
embedStr->rewind(); | |
} | |
if (dumpJPEG && str->getKind() == strDCT) { | |
// dump JPEG file | |
writeRawImage(str, "jpg"); | |
} else if (dumpJP2 && str->getKind() == strJPX && !inlineImg) { | |
// dump JPEG2000 file | |
writeRawImage(str, "jp2"); | |
} else if (dumpJBIG2 && str->getKind() == strJBIG2 && !inlineImg) { | |
// dump JBIG2 globals stream if available | |
JBIG2Stream *jb2Str = static_cast<JBIG2Stream *>(str); | |
Object *globals = jb2Str->getGlobalsStream(); | |
if (globals->isStream()) { | |
FILE *f; | |
int c; | |
Stream *globalsStr = globals->getStream(); | |
setFilename("jb2g"); | |
if (!(f = fopen(fileName, "wb"))) { | |
error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); | |
errorCode = 2; | |
return; | |
} | |
globalsStr->reset(); | |
while ((c = globalsStr->getChar()) != EOF) { | |
fputc(c, f); | |
} | |
globalsStr->close(); | |
fclose(f); | |
} | |
// dump JBIG2 embedded file | |
writeRawImage(str, "jb2e"); | |
} else if (dumpCCITT && str->getKind() == strCCITTFax) { | |
// write CCITT parameters | |
CCITTFaxStream *ccittStr = static_cast<CCITTFaxStream *>(str); | |
FILE *f; | |
setFilename("params"); | |
if (!(f = fopen(fileName, "wb"))) { | |
error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); | |
errorCode = 2; | |
return; | |
} | |
if (ccittStr->getEncoding() < 0) { | |
fprintf(f, "-4 "); | |
} else if (ccittStr->getEncoding() == 0) { | |
fprintf(f, "-1 "); | |
} else { | |
fprintf(f, "-2 "); | |
} | |
if (ccittStr->getEndOfLine()) { | |
fprintf(f, "-A "); | |
} else { | |
fprintf(f, "-P "); | |
} | |
fprintf(f, "-X %d ", ccittStr->getColumns()); | |
if (ccittStr->getBlackIs1()) { | |
fprintf(f, "-W "); | |
} else { | |
fprintf(f, "-B "); | |
} | |
fprintf(f, "-M\n"); // PDF uses MSB first | |
fclose(f); | |
// dump CCITT file | |
writeRawImage(str, "ccitt"); | |
} else if (outputPNG && !(outputTiff && colorMap && (colorMap->getColorSpace()->getMode() == csDeviceCMYK || (colorMap->getColorSpace()->getMode() == csICCBased && colorMap->getNumPixelComps() == 4)))) { | |
// output in PNG format | |
ImgWriter *writer; | |
if (!colorMap || (colorMap->getNumPixelComps() == 1 && colorMap->getBits() == 1)) { | |
writer = new PNGWriter(PNGWriter::MONOCHROME); | |
format = imgMonochrome; | |
} else if (colorMap->getColorSpace()->getMode() == csDeviceGray || colorMap->getColorSpace()->getMode() == csCalGray) { | |
writer = new PNGWriter(PNGWriter::GRAY); | |
format = imgGray; | |
} else if ((colorMap->getColorSpace()->getMode() == csDeviceRGB || colorMap->getColorSpace()->getMode() == csCalRGB || (colorMap->getColorSpace()->getMode() == csICCBased && colorMap->getNumPixelComps() == 3)) | |
&& colorMap->getBits() > 8) { | |
writer = new PNGWriter(PNGWriter::RGB48); | |
format = imgRGB48; | |
} else { | |
writer = new PNGWriter(PNGWriter::RGB); | |
format = imgRGB; | |
} | |
writeImageFile(writer, format, "png", str, width, height, colorMap); | |
delete writer; | |
} else if (outputTiff) { | |
// output in TIFF format | |
ImgWriter *writer; | |
if (!colorMap || (colorMap->getNumPixelComps() == 1 && colorMap->getBits() == 1)) { | |
writer = new TiffWriter(TiffWriter::MONOCHROME); | |
format = imgMonochrome; | |
} else if (colorMap->getColorSpace()->getMode() == csDeviceGray || colorMap->getColorSpace()->getMode() == csCalGray) { | |
writer = new TiffWriter(TiffWriter::GRAY); | |
format = imgGray; | |
} else if (colorMap->getColorSpace()->getMode() == csDeviceCMYK || (colorMap->getColorSpace()->getMode() == csICCBased && colorMap->getNumPixelComps() == 4)) { | |
writer = new TiffWriter(TiffWriter::CMYK); | |
format = imgCMYK; | |
} else if ((colorMap->getColorSpace()->getMode() == csDeviceRGB || colorMap->getColorSpace()->getMode() == csCalRGB || (colorMap->getColorSpace()->getMode() == csICCBased && colorMap->getNumPixelComps() == 3)) | |
&& colorMap->getBits() > 8) { | |
writer = new TiffWriter(TiffWriter::RGB48); | |
format = imgRGB48; | |
} else { | |
writer = new TiffWriter(TiffWriter::RGB); | |
format = imgRGB; | |
} | |
writeImageFile(writer, format, "tif", str, width, height, colorMap); | |
delete writer; | |
} else { | |
// output in PPM/PBM format | |
ImgWriter *writer; | |
if (!colorMap || (colorMap->getNumPixelComps() == 1 && colorMap->getBits() == 1)) { | |
writer = new NetPBMWriter(NetPBMWriter::MONOCHROME); | |
format = imgMonochrome; | |
} else { | |
writer = new NetPBMWriter(NetPBMWriter::RGB); | |
format = imgRGB; | |
} | |
writeImageFile(writer, format, format == imgRGB ? "ppm" : "pbm", str, width, height, colorMap); | |
delete writer; | |
} | |
if (inlineImg) { | |
embedStr->restore(); | |
} | |
if (printFilenames) { | |
printf("%s\n", fileName); | |
} | |
} | |
bool ImageOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Catalog *cat, GfxTilingPattern *tPat, const double *mat, int x0, int y0, int x1, int y1, double xStep, double yStep) | |
{ | |
return true; | |
// do nothing -- this avoids the potentially slow loop in Gfx.cc | |
} | |
void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, bool invert, bool interpolate, bool inlineImg) | |
{ | |
if (listImages) { | |
listImage(state, ref, str, width, height, nullptr, interpolate, inlineImg, imgStencil); | |
} else { | |
writeImage(state, ref, str, width, height, nullptr, inlineImg); | |
} | |
} | |
void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, const int *maskColors, bool inlineImg) | |
{ | |
if (listImages) { | |
listImage(state, ref, str, width, height, colorMap, interpolate, inlineImg, imgImage); | |
} else { | |
writeImage(state, ref, str, width, height, colorMap, inlineImg); | |
} | |
} | |
void ImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, bool maskInvert, bool maskInterpolate) | |
{ | |
if (listImages) { | |
listImage(state, ref, str, width, height, colorMap, interpolate, false, imgImage); | |
listImage(state, ref, maskStr, maskWidth, maskHeight, nullptr, maskInterpolate, false, imgMask); | |
} else { | |
writeImage(state, ref, str, width, height, colorMap, false); | |
writeImage(state, ref, maskStr, maskWidth, maskHeight, nullptr, false); | |
} | |
} | |
void ImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, | |
bool maskInterpolate) | |
{ | |
if (listImages) { | |
listImage(state, ref, str, width, height, colorMap, interpolate, false, imgImage); | |
listImage(state, ref, maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate, false, imgSmask); | |
} else { | |
writeImage(state, ref, str, width, height, colorMap, false); | |
writeImage(state, ref, maskStr, maskWidth, maskHeight, maskColorMap, false); | |
} | |
} | |