diff options
Diffstat (limited to 'src/libs/dimg/loaders/jpegloader.cpp')
-rw-r--r-- | src/libs/dimg/loaders/jpegloader.cpp | 676 |
1 files changed, 676 insertions, 0 deletions
diff --git a/src/libs/dimg/loaders/jpegloader.cpp b/src/libs/dimg/loaders/jpegloader.cpp new file mode 100644 index 00000000..58f6e20a --- /dev/null +++ b/src/libs/dimg/loaders/jpegloader.cpp @@ -0,0 +1,676 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2005-06-14 + * Description : A JPEG IO file for DImg framework + * + * Copyright (C) 2005 by Renchi Raju <[email protected]> + * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * This program is free software; you can redistribute it + * and/or modify it under the terms of the GNU General + * Public License as published by the Free Software Foundation; + * either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ============================================================ */ + +#define XMD_H + +// This line must be commented to prevent any latency time +// when we use threaded image loader interface for each image +// files io. Uncomment this line only for debugging. +//#define ENABLE_DEBUG_MESSAGES + +// C ansi includes. + +extern "C" +{ +#include "iccjpeg.h" +} + +// C+ includes. + +#include <cstdio> +#include <cstdlib> + +// TQt includes. + +#include <tqfile.h> +#include <tqcstring.h> + +// Local includes. + +#include "ddebug.h" +#include "dimg.h" +#include "dimgloaderobserver.h" +#include "jpegloader.h" + +namespace Digikam +{ + +// To manage Errors/Warnings handling provide by libjpeg + +void JPEGLoader::dimg_jpeg_error_exit(j_common_ptr cinfo) +{ + dimg_jpeg_error_mgr* myerr = (dimg_jpeg_error_mgr*) cinfo->err; + + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << endl; +#endif + + longjmp(myerr->setjmp_buffer, 1); +} + +void JPEGLoader::dimg_jpeg_emit_message(j_common_ptr cinfo, int msg_level) +{ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << " (" << msg_level << ")" << endl; +#else + Q_UNUSED(msg_level); +#endif +} + +void JPEGLoader::dimg_jpeg_output_message(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << endl; +#endif +} + +JPEGLoader::JPEGLoader(DImg* image) + : DImgLoader(image) +{ +} + +bool JPEGLoader::load(const TQString& filePath, DImgLoaderObserver *observer) +{ + readMetadata(filePath, DImg::JPEG); + + FILE *file = fopen(TQFile::encodeName(filePath), "rb"); + if (!file) + return false; + + unsigned char header[2]; + + if (fread(&header, 2, 1, file) != 1) + { + fclose(file); + return false; + } + + unsigned char jpegID[] = { 0xFF, 0xD8 }; + + if (memcmp(header, jpegID, 2) != 0) + { + // not a jpeg file + fclose(file); + return false; + } + + fseek(file, 0L, SEEK_SET); + + struct jpeg_decompress_struct cinfo; + struct dimg_jpeg_error_mgr jerr; + + // ------------------------------------------------------------------- + // JPEG error handling. + + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = dimg_jpeg_error_exit; + cinfo.err->emit_message = dimg_jpeg_emit_message; + cinfo.err->output_message = dimg_jpeg_output_message; + + // If an error occurs during reading, libjpeg will jump here + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + return false; + } + + // ------------------------------------------------------------------- + // Find out if we do the fast-track loading with reduced size. Jpeg specific. + int scaledLoadingSize = 0; + TQVariant attribute = imageGetAttribute("jpegScaledLoadingSize"); + if (attribute.isValid()) + scaledLoadingSize = attribute.toInt(); + + // ------------------------------------------------------------------- + // Set JPEG decompressor instance + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, file); + + // Recording ICC profile marker (from iccjpeg.c) + setup_read_icc_profile(&cinfo); + + // read image information + jpeg_read_header(&cinfo, true); + + // set decompression parameters + cinfo.do_fancy_upsampling = false; + cinfo.do_block_smoothing = false; + + if (scaledLoadingSize) + { + int imgSize = TQMAX(cinfo.image_width, cinfo.image_height); + + // libjpeg supports 1/1, 1/2, 1/4, 1/8 + int scale=1; + while(scaledLoadingSize*scale*2<=imgSize) + { + scale*=2; + } + if(scale>8) scale=8; + + cinfo.scale_num=1; + cinfo.scale_denom=scale; + } + + // Libjpeg handles the following conversions: + // YCbCr => GRAYSCALE, YCbCr => RGB, GRAYSCALE => RGB, YCCK => CMYK + // So we cannot get RGB from CMYK or YCCK, CMYK conversion is handled below + switch (cinfo.jpeg_color_space) + { + case JCS_UNKNOWN: + // perhaps jpeg_read_header did some guessing, leave value unchanged + break; + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + cinfo.out_color_space = JCS_RGB; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo.out_color_space = JCS_CMYK; + break; + } + + // initialize decompression + jpeg_start_decompress(&cinfo); + + // some pseudo-progress + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Get image data. + + int w = cinfo.output_width; + int h = cinfo.output_height; + uchar *dest = 0; + + uchar *ptr, *line[16], *data=0; + uchar *ptr2=0; + int x, y, l, i, scans, count, prevy; + + if (cinfo.rec_outbuf_height > 16) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo << "Height of JPEG scanline buffer out of range!" << endl; + return false; + } + + // We only take RGB with 1 or 3 components, or CMYK with 4 components + if (!( + (cinfo.out_color_space == JCS_RGB && (cinfo.output_components == 3 || cinfo.output_components == 1)) + || (cinfo.out_color_space == JCS_CMYK && cinfo.output_components == 4) + )) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo + << "JPEG colorspace (" + << cinfo.out_color_space + << ") or Number of JPEG color components (" + << cinfo.output_components + << ") unsupported!" << endl; + return false; + } + + data = new uchar[w * 16 * cinfo.output_components]; + + if (!data) + { + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo << "Cannot allocate memory!" << endl; + return false; + } + + dest = new uchar[w * h * 4]; + + if (!dest) + { + delete [] data; + jpeg_destroy_decompress(&cinfo); + fclose(file); + DDebug() << k_funcinfo << "Cannot allocate memory!" << endl; + return false; + } + + ptr2 = dest; + count = 0; + prevy = 0; + + if (cinfo.output_components == 3) + { + for (i = 0; i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w * 3); + + int checkPoint = 0; + for (l = 0; l < h; l += cinfo.rec_outbuf_height) + { + // use 0-10% and 90-100% for pseudo-progress + if (observer && l >= checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] dest; + jpeg_destroy_decompress(&cinfo); + fclose(file); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)l)/((float)h) ))); + } + + jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); + scans = cinfo.rec_outbuf_height; + + if ((h - l) < scans) + scans = h - l; + + ptr = data; + + for (y = 0; y < scans; y++) + { + for (x = 0; x < w; x++) + { + ptr2[3] = 0xFF; + ptr2[2] = ptr[0]; + ptr2[1] = ptr[1]; + ptr2[0] = ptr[2]; + + ptr += 3; + ptr2 += 4; + } + } + } + } + else if (cinfo.output_components == 1) + { + for (i = 0; i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w); + + int checkPoint = 0; + for (l = 0; l < h; l += cinfo.rec_outbuf_height) + { + if (observer && l >= checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] dest; + jpeg_destroy_decompress(&cinfo); + fclose(file); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)l)/((float)h) ))); + } + + jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); + scans = cinfo.rec_outbuf_height; + + if ((h - l) < scans) + scans = h - l; + + ptr = data; + + for (y = 0; y < scans; y++) + { + for (x = 0; x < w; x++) + { + ptr2[3] = 0xFF; + ptr2[2] = ptr[0]; + ptr2[1] = ptr[0]; + ptr2[0] = ptr[0]; + + ptr ++; + ptr2 += 4; + } + } + } + } + else // CMYK + { + for (i = 0; i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w * 4); + + int checkPoint = 0; + for (l = 0; l < h; l += cinfo.rec_outbuf_height) + { + // use 0-10% and 90-100% for pseudo-progress + if (observer && l >= checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] data; + delete [] dest; + jpeg_destroy_decompress(&cinfo); + fclose(file); + return false; + } + observer->progressInfo(m_image, 0.1 + (0.8 * ( ((float)l)/((float)h) ))); + } + + jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); + scans = cinfo.rec_outbuf_height; + + if ((h - l) < scans) + scans = h - l; + + ptr = data; + + for (y = 0; y < scans; y++) + { + for (x = 0; x < w; x++) + { + // Inspired by TQt's JPEG loader + + int k = ptr[3]; + + ptr2[3] = 0xFF; + ptr2[2] = k * ptr[0] / 255; + ptr2[1] = k * ptr[1] / 255; + ptr2[0] = k * ptr[2] / 255; + + ptr += 4; + ptr2 += 4; + } + } + } + } + + delete [] data; + + // ------------------------------------------------------------------- + // Read image ICC profile + + TQMap<int, TQByteArray>& metaData = imageMetaData(); + + JOCTET *profile_data=NULL; + uint profile_size; + + read_icc_profile (&cinfo, &profile_data, &profile_size); + + if (profile_data != NULL) + { + TQByteArray profile_rawdata(profile_size); + memcpy(profile_rawdata.data(), profile_data, profile_size); + metaData.insert(DImg::ICC, profile_rawdata); + free (profile_data); + } + else + { + // If ICC profile is null, check Exif metadata. + checkExifWorkingColorSpace(); + } + + // ------------------------------------------------------------------- + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + // ------------------------------------------------------------------- + + fclose(file); + + if (observer) + observer->progressInfo(m_image, 1.0); + + imageWidth() = w; + imageHeight() = h; + imageData() = dest; + imageSetAttribute("format", "JPEG"); + + return true; +} + +bool JPEGLoader::save(const TQString& filePath, DImgLoaderObserver *observer) +{ + FILE *file = fopen(TQFile::encodeName(filePath), "wb"); + if (!file) + return false; + + struct jpeg_compress_struct cinfo; + struct dimg_jpeg_error_mgr jerr; + + // ------------------------------------------------------------------- + // JPEG error handling. + + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = dimg_jpeg_error_exit; + cinfo.err->emit_message = dimg_jpeg_emit_message; + cinfo.err->output_message = dimg_jpeg_output_message; + + // If an error occurs during writing, libjpeg will jump here + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_compress(&cinfo); + fclose(file); + return false; + } + + // ------------------------------------------------------------------- + // Set JPEG compressor instance + + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, file); + + uint& w = imageWidth(); + uint& h = imageHeight(); + unsigned char*& data = imageData(); + + // Size of image. + cinfo.image_width = w; + cinfo.image_height = h; + + // Color components of image in RGB. + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + TQVariant qualityAttr = imageGetAttribute("quality"); + int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; + + if (quality < 0) + quality = 90; + if (quality > 100) + quality = 100; + + TQVariant subSamplingAttr = imageGetAttribute("subsampling"); + int subsampling = subSamplingAttr.isValid() ? subSamplingAttr.toInt() : 1; // Medium + + jpeg_set_defaults(&cinfo); + + // B.K.O #149578: set horizontal and vertical chroma subsampling factor to encoder. + // See this page for details: http://en.wikipedia.org/wiki/Chroma_subsampling + + switch (subsampling) + { + case 1: // 2x1, 1x1, 1x1 (4:2:2) : Medium + { + DDebug() << "Using LibJPEG medium chroma-subsampling (4:2:2)" << endl; + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + } + case 2: // 2x2, 1x1, 1x1 (4:1:1) : High + { + DDebug() << "Using LibJPEG high chroma-subsampling (4:1:1)" << endl; + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 2; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + } + default: // 1x1 1x1 1x1 (4:4:4) : None + { + DDebug() << "Using LibJPEG none chroma-subsampling (4:4:4)" << endl; + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + break; + } + } + + jpeg_set_quality(&cinfo, quality, true); + jpeg_start_compress(&cinfo, true); + + DDebug() << "Using LibJPEG quality compression value: " << quality << endl; + + if (observer) + observer->progressInfo(m_image, 0.1); + + // ------------------------------------------------------------------- + // Write ICC profil. + + TQByteArray profile_rawdata = m_image->getICCProfil(); + + if (!profile_rawdata.isEmpty()) + { + write_icc_profile (&cinfo, (JOCTET *)profile_rawdata.data(), profile_rawdata.size()); + } + + if (observer) + observer->progressInfo(m_image, 0.2); + + // ------------------------------------------------------------------- + // Write Image data. + + uchar* line = new uchar[w*3]; + uchar* dstPtr = 0; + uint checkPoint = 0; + + if (!imageSixteenBit()) // 8 bits image. + { + + uchar* srcPtr = data; + + for (uint j=0; j<h; j++) + { + + if (observer && j == checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] line; + jpeg_destroy_compress(&cinfo); + fclose(file); + return false; + } + // use 0-20% for pseudo-progress, now fill 20-100% + observer->progressInfo(m_image, 0.2 + (0.8 * ( ((float)j)/((float)h) ))); + } + + dstPtr = line; + + for (uint i = 0; i < w; i++) + { + dstPtr[2] = srcPtr[0]; + dstPtr[1] = srcPtr[1]; + dstPtr[0] = srcPtr[2]; + + srcPtr += 4; + dstPtr += 3; + } + + jpeg_write_scanlines(&cinfo, &line, 1); + } + } + else + { + unsigned short* srcPtr = (unsigned short*)data; + + for (uint j=0; j<h; j++) + { + + if (observer && j == checkPoint) + { + checkPoint += granularity(observer, h, 0.8); + if (!observer->continueQuery(m_image)) + { + delete [] line; + jpeg_destroy_compress(&cinfo); + fclose(file); + return false; + } + // use 0-20% for pseudo-progress, now fill 20-100% + observer->progressInfo(m_image, 0.2 + (0.8 * ( ((float)j)/((float)h) ))); + } + + dstPtr = line; + + for (uint i = 0; i < w; i++) + { + dstPtr[2] = (srcPtr[0] * 255UL)/65535UL; + dstPtr[1] = (srcPtr[1] * 255UL)/65535UL; + dstPtr[0] = (srcPtr[2] * 255UL)/65535UL; + + srcPtr += 4; + dstPtr += 3; + } + + jpeg_write_scanlines(&cinfo, &line, 1); + } + } + + delete [] line; + + // ------------------------------------------------------------------- + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + fclose(file); + + imageSetAttribute("savedformat", "JPEG"); + + saveMetadata(filePath); + + return true; +} + +} // NameSpace Digikam |