blob: 850eb102f35b02dab904e524bcd1384f200998b6 [file] [log] [blame]
//========================================================================
//
// 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 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2006 Rainer Keller <class321@gmx.de>
// Copyright (C) 2008 Timothy Lee <timothy.lee@siriushk.com>
// Copyright (C) 2008 Vasile Gaburici <gaburici@cs.umd.edu>
// Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2009 William Bader <williambader@hotmail.com>
// Copyright (C) 2010 Jakob Voss <jakob.voss@gbv.de>
// Copyright (C) 2012, 2013, 2017, 2018 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2013 Thomas Fischer <fischer@unix-ag.uni-kl.de>
// Copyright (C) 2013 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2017 Caolán McNamara <caolanm@redhat.com>
// Copyright (C) 2018 Andreas Gruenbacher <agruenba@redhat.com>
//
// 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
//
//========================================================================
#include "config.h"
#include <poppler-config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <ctype.h>
#include <math.h>
#include "goo/gmem.h"
#include "goo/NetPBMWriter.h"
#include "goo/PNGWriter.h"
#include "goo/TiffWriter.h"
#include "Error.h"
#include "GfxState.h"
#include "Object.h"
#include "Stream.h"
#include "JBIG2Stream.h"
#include "ImageOutputDev.h"
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;
imgNum = 0;
pageNum = 0;
ok = true;
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 = mat[0] + mat[2];
double height2 = mat[1] + mat[3];
double xppi = fabs(width*72.0/width2) + 0.5;
double yppi = fabs(height*72.0/height2) + 0.5;
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);
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);
return;
}
if (!writer->init(f, width, height, 72, 72)) {
error(errIO, -1, "Error writing '{0:s}'", fileName);
return;
}
}
if (format != imgMonochrome) {
// initialize stream
imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
colorMap->getBits());
imgStr->reset();
} else {
// initialize stream
str->reset();
}
int pixelSize = sizeof(unsigned int);
if (format == imgRGB48)
pixelSize = 2*sizeof(unsigned int);
row = (unsigned char *) gmallocn(width, pixelSize);
// 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);
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);
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
#ifdef ENABLE_LIBPNG
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;
#endif
} else if (outputTiff) {
// output in TIFF format
#ifdef ENABLE_LIBTIFF
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;
#endif
} 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();
}
bool ImageOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Catalog *cat, Object *str,
const double *pmat, int paintType, int tilingType, Dict *resDict,
const double *mat, const double *bbox,
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, 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, str, 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);
}
}