| //======================================================================== |
| // |
| // JpegWriter.cc |
| // |
| // This file is licensed under the GPLv2 or later |
| // |
| // Copyright (C) 2009 Stefan Thomas <thomas@eload24.com> |
| // Copyright (C) 2010, 2012, 2017 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2010 Harry Roberts <harry.roberts@midnight-labs.org> |
| // Copyright (C) 2011 Thomas Freitag <Thomas.Freitag@alfa.de> |
| // Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de> |
| // Copyright (C) 2017, 2018, 2022 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2018 Martin Packman <gzlist@googlemail.com> |
| // Copyright (C) 2018 Ed Porras <ed@motologic.com> |
| // Copyright (C) 2021 Peter Williams <peter@newton.cx> |
| // Copyright (C) 2023 Jordan Abrahams-Whitehead <ajordanr@google.com> |
| // |
| //======================================================================== |
| |
| #include "JpegWriter.h" |
| |
| #include <limits> |
| |
| #ifdef ENABLE_LIBJPEG |
| |
| # include "poppler/Error.h" |
| # include <cstdio> |
| extern "C" { |
| # include <jpeglib.h> |
| } |
| |
| struct JpegWriterPrivate |
| { |
| bool progressive; |
| bool optimize; |
| int quality; |
| JpegWriter::Format format; |
| struct jpeg_compress_struct cinfo; |
| struct jpeg_error_mgr jerr; |
| }; |
| |
| static void outputMessage(j_common_ptr cinfo) |
| { |
| char buffer[JMSG_LENGTH_MAX]; |
| |
| // Create the message |
| (*cinfo->err->format_message)(cinfo, buffer); |
| |
| // Send it to poppler's error handler |
| error(errInternal, -1, "{0:s}", buffer); |
| } |
| |
| JpegWriter::JpegWriter(int q, bool p, Format formatA) |
| { |
| priv = new JpegWriterPrivate; |
| priv->progressive = p; |
| priv->optimize = false; |
| priv->quality = q; |
| priv->format = formatA; |
| } |
| |
| JpegWriter::JpegWriter(Format formatA) : JpegWriter(-1, false, formatA) { } |
| |
| JpegWriter::~JpegWriter() |
| { |
| // cleanup |
| jpeg_destroy_compress(&priv->cinfo); |
| delete priv; |
| } |
| |
| void JpegWriter::setQuality(int quality) |
| { |
| priv->quality = quality; |
| } |
| |
| void JpegWriter::setProgressive(bool progressive) |
| { |
| priv->progressive = progressive; |
| } |
| |
| void JpegWriter::setOptimize(bool optimize) |
| { |
| priv->optimize = optimize; |
| } |
| |
| bool JpegWriter::init(FILE *f, int width, int height, double hDPI, double vDPI) |
| { |
| if (hDPI < 0 || vDPI < 0 || hDPI > std::numeric_limits<UINT16>::max() || vDPI > std::numeric_limits<UINT16>::max()) { |
| error(errInternal, -1, "JpegWriter::init: hDPI or vDPI values are invalid {0:f} {1:f}", hDPI, vDPI); |
| return false; |
| } |
| |
| // Setup error handler |
| priv->cinfo.err = jpeg_std_error(&priv->jerr); |
| priv->jerr.output_message = &outputMessage; |
| |
| // Initialize libjpeg |
| jpeg_create_compress(&priv->cinfo); |
| |
| // First set colorspace and call jpeg_set_defaults() since |
| // jpeg_set_defaults() sets default values for all fields in |
| // cinfo based on the colorspace. |
| switch (priv->format) { |
| case RGB: |
| priv->cinfo.in_color_space = JCS_RGB; |
| break; |
| case GRAY: |
| priv->cinfo.in_color_space = JCS_GRAYSCALE; |
| break; |
| case CMYK: |
| priv->cinfo.in_color_space = JCS_CMYK; |
| break; |
| default: |
| return false; |
| } |
| jpeg_set_defaults(&priv->cinfo); |
| |
| // Set destination file |
| jpeg_stdio_dest(&priv->cinfo, f); |
| |
| // Set libjpeg configuration |
| priv->cinfo.image_width = width; |
| priv->cinfo.image_height = height; |
| priv->cinfo.density_unit = 1; // dots per inch |
| priv->cinfo.X_density = static_cast<UINT16>(hDPI); |
| priv->cinfo.Y_density = static_cast<UINT16>(vDPI); |
| switch (priv->format) { |
| case GRAY: |
| priv->cinfo.input_components = 1; |
| break; |
| case RGB: |
| priv->cinfo.input_components = 3; |
| break; |
| case CMYK: |
| priv->cinfo.input_components = 4; |
| jpeg_set_colorspace(&priv->cinfo, JCS_YCCK); |
| priv->cinfo.write_JFIF_header = TRUE; |
| break; |
| default: |
| return false; |
| } |
| |
| // Set quality |
| if (priv->quality >= 0 && priv->quality <= 100) { |
| jpeg_set_quality(&priv->cinfo, priv->quality, TRUE); |
| } |
| |
| // Use progressive mode |
| if (priv->progressive) { |
| jpeg_simple_progression(&priv->cinfo); |
| } |
| |
| // Set whether to compute optimal Huffman coding tables |
| priv->cinfo.optimize_coding = static_cast<boolean>(priv->optimize); |
| |
| // Get ready for data |
| jpeg_start_compress(&priv->cinfo, TRUE); |
| |
| return true; |
| } |
| |
| bool JpegWriter::writePointers(unsigned char **rowPointers, int rowCount) |
| { |
| if (priv->format == CMYK) { |
| for (int y = 0; y < rowCount; y++) { |
| unsigned char *row = rowPointers[y]; |
| for (unsigned int x = 0; x < priv->cinfo.image_width; x++) { |
| for (int n = 0; n < 4; n++) { |
| *row = 0xff - *row; |
| row++; |
| } |
| } |
| } |
| } |
| // Write all rows to the file |
| jpeg_write_scanlines(&priv->cinfo, rowPointers, rowCount); |
| |
| return true; |
| } |
| |
| bool JpegWriter::writeRow(unsigned char **rowPointer) |
| { |
| if (priv->format == CMYK) { |
| unsigned char *row = rowPointer[0]; |
| for (unsigned int x = 0; x < priv->cinfo.image_width; x++) { |
| for (int n = 0; n < 4; n++) { |
| *row = 0xff - *row; |
| row++; |
| } |
| } |
| } |
| // Write the row to the file |
| jpeg_write_scanlines(&priv->cinfo, rowPointer, 1); |
| |
| return true; |
| } |
| |
| bool JpegWriter::close() |
| { |
| jpeg_finish_compress(&priv->cinfo); |
| |
| return true; |
| } |
| |
| bool JpegWriter::supportCMYK() |
| { |
| return priv->format == CMYK; |
| } |
| |
| #endif |