diff options
Diffstat (limited to 'src/libs/jpegutils/jpegutils.cpp')
-rw-r--r-- | src/libs/jpegutils/jpegutils.cpp | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/src/libs/jpegutils/jpegutils.cpp b/src/libs/jpegutils/jpegutils.cpp new file mode 100644 index 00000000..2f2aabb6 --- /dev/null +++ b/src/libs/jpegutils/jpegutils.cpp @@ -0,0 +1,532 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * http://www.digikam.org + * + * Date : 2004-09-29 + * Description : perform lossless rotation/flip to JPEG file + * + * Copyright (C) 2004-2005 by Renchi Raju <[email protected]> + * Copyright (C) 2006-2009 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 + +// C++ includes. + +#include <cstdio> +#include <cstdlib> + +// C Ansi includes. + +extern "C" +{ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <utime.h> +#include <setjmp.h> +#include <jpeglib.h> +} + +// TQt includes. + +#include <tqcstring.h> +#include <tqfile.h> +#include <tqfileinfo.h> + +// Local includes. + +#include "ddebug.h" +#include "dmetadata.h" +#include "transupp.h" +#include "jpegutils.h" + +namespace Digikam +{ + +// To manage Errors/Warnings handling provide by libjpeg + +//#define ENABLE_DEBUG_MESSAGES + +struct jpegutils_jpeg_error_mgr : public jpeg_error_mgr +{ + jmp_buf setjmp_buffer; +}; + +static void jpegutils_jpeg_error_exit(j_common_ptr cinfo); +static void jpegutils_jpeg_emit_message(j_common_ptr cinfo, int msg_level); +static void jpegutils_jpeg_output_message(j_common_ptr cinfo); + +static void jpegutils_jpeg_error_exit(j_common_ptr cinfo) +{ + jpegutils_jpeg_error_mgr* myerr = (jpegutils_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); +} + +static void jpegutils_jpeg_emit_message(j_common_ptr cinfo, int msg_level) +{ + Q_UNUSED(msg_level) + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + +#ifdef ENABLE_DEBUG_MESSAGES + DDebug() << k_funcinfo << buffer << " (" << msg_level << ")" << endl; +#endif +} + +static void jpegutils_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 +} + +bool loadJPEGScaled(TQImage& image, const TQString& path, int maximumSize) +{ + TQString format = TQImageIO::imageFormat(path); + if (format !="JPEG") return false; + + FILE* inputFile=fopen(TQFile::encodeName(path), "rb"); + if(!inputFile) + return false; + + struct jpeg_decompress_struct cinfo; + struct jpegutils_jpeg_error_mgr jerr; + + // JPEG error handling - thanks to Marcus Meissner + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = jpegutils_jpeg_error_exit; + cinfo.err->emit_message = jpegutils_jpeg_emit_message; + cinfo.err->output_message = jpegutils_jpeg_output_message; + + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + fclose(inputFile); + return false; + } + + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, inputFile); + jpeg_read_header(&cinfo, true); + + int imgSize = TQMAX(cinfo.image_width, cinfo.image_height); + + // libjpeg supports 1/1, 1/2, 1/4, 1/8 + int scale=1; + while(maximumSize*scale*2<=imgSize) + { + scale*=2; + } + if(scale>8) scale=8; + + cinfo.scale_num=1; + cinfo.scale_denom=scale; + + switch (cinfo.jpeg_color_space) + { + case JCS_UNKNOWN: + 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; + } + + jpeg_start_decompress(&cinfo); + + TQImage img; + + // 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(inputFile); + return false; + } + + switch(cinfo.output_components) + { + case 3: + case 4: + img.create( cinfo.output_width, cinfo.output_height, 32 ); + break; + case 1: // B&W image + img.create( cinfo.output_width, cinfo.output_height, 8, 256 ); + for (int i = 0 ; i < 256 ; i++) + img.setColor(i, tqRgb(i, i, i)); + break; + } + + uchar** lines = img.jumpTable(); + while (cinfo.output_scanline < cinfo.output_height) + jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, cinfo.output_height); + + jpeg_finish_decompress(&cinfo); + + // Expand 24->32 bpp + if ( cinfo.output_components == 3 ) + { + for (uint j=0; j<cinfo.output_height; j++) + { + uchar *in = img.scanLine(j) + cinfo.output_width*3; + TQRgb *out = (TQRgb*)( img.scanLine(j) ); + + for (uint i=cinfo.output_width; i--; ) + { + in -= 3; + out[i] = tqRgb(in[0], in[1], in[2]); + } + } + } + else if ( cinfo.output_components == 4 ) + { + // CMYK conversion + for (uint j=0; j<cinfo.output_height; j++) + { + uchar *in = img.scanLine(j) + cinfo.output_width*4; + TQRgb *out = (TQRgb*)( img.scanLine(j) ); + + for (uint i=cinfo.output_width; i--; ) + { + in -= 4; + int k = in[3]; + out[i] = tqRgb(k * in[0] / 255, k * in[1] / 255, k * in[2] / 255); + } + } + } + + int newMax = TQMAX(cinfo.output_width, cinfo.output_height); + int newx = maximumSize*cinfo.output_width / newMax; + int newy = maximumSize*cinfo.output_height / newMax; + + jpeg_destroy_decompress(&cinfo); + fclose(inputFile); + + image = img; + + return true; +} + +bool exifRotate(const TQString& file, const TQString& documentName) +{ + TQFileInfo fi(file); + if (!fi.exists()) + { + DDebug() << "ExifRotate: file do not exist: " << file << endl; + return false; + } + + if (isJpegImage(file)) + { + DMetadata metaData; + if (!metaData.load(file)) + { + DDebug() << "ExifRotate: no Exif data found: " << file << endl; + return true; + } + + TQString temp(fi.dirPath(true) + "/.digikam-exifrotate-"); + temp.append(TQString::number(getpid())); + temp.append(TQString(".jpg")); + + TQCString in = TQFile::encodeName(file); + TQCString out = TQFile::encodeName(temp); + + JCOPY_OPTION copyoption = JCOPYOPT_ALL; + jpeg_transform_info transformoption; + memset(&transformoption, 0, sizeof(jpeg_transform_info)); + + transformoption.force_grayscale = false; + transformoption.trim = false; + transformoption.transform = JXFORM_NONE; + + // we have the exif info. check the orientation + + switch(metaData.getImageOrientation()) + { + case(DMetadata::ORIENTATION_UNSPECIFIED): + case(DMetadata::ORIENTATION_NORMAL): + break; + case(DMetadata::ORIENTATION_HFLIP): + { + transformoption.transform = JXFORM_FLIP_H; + break; + } + case(DMetadata::ORIENTATION_ROT_180): + { + transformoption.transform = JXFORM_ROT_180; + break; + } + case(DMetadata::ORIENTATION_VFLIP): + { + transformoption.transform = JXFORM_FLIP_V; + break; + } + case(DMetadata::ORIENTATION_ROT_90_HFLIP): + { + transformoption.transform = JXFORM_TRANSPOSE; + break; + } + case(DMetadata::ORIENTATION_ROT_90): + { + transformoption.transform = JXFORM_ROT_90; + break; + } + case(DMetadata::ORIENTATION_ROT_90_VFLIP): + { + transformoption.transform = JXFORM_TRANSVERSE; + break; + } + case(DMetadata::ORIENTATION_ROT_270): + { + transformoption.transform = JXFORM_ROT_270; + break; + } + } + + if (transformoption.transform == JXFORM_NONE) + { + DDebug() << "ExifRotate: no rotation to perform: " << file << endl; + return true; + } + + struct jpeg_decompress_struct srcinfo; + struct jpeg_compress_struct dstinfo; + struct jpegutils_jpeg_error_mgr jsrcerr, jdsterr; + jvirt_barray_ptr* src_coef_arrays; + jvirt_barray_ptr* dst_coef_arrays; + + // Initialize the JPEG decompression object with default error handling + srcinfo.err = jpeg_std_error(&jsrcerr); + srcinfo.err->error_exit = jpegutils_jpeg_error_exit; + srcinfo.err->emit_message = jpegutils_jpeg_emit_message; + srcinfo.err->output_message = jpegutils_jpeg_output_message; + + // Initialize the JPEG compression object with default error handling + dstinfo.err = jpeg_std_error(&jdsterr); + dstinfo.err->error_exit = jpegutils_jpeg_error_exit; + dstinfo.err->emit_message = jpegutils_jpeg_emit_message; + dstinfo.err->output_message = jpegutils_jpeg_output_message; + + FILE *input_file; + FILE *output_file; + + input_file = fopen(in, "rb"); + if (!input_file) + { + DWarning() << "ExifRotate: Error in opening input file: " << input_file << endl; + return false; + } + + output_file = fopen(out, "wb"); + if (!output_file) + { + fclose(input_file); + DWarning() << "ExifRotate: Error in opening output file: " << output_file << endl; + return false; + } + + if (setjmp(jsrcerr.setjmp_buffer) || setjmp(jdsterr.setjmp_buffer)) + { + jpeg_destroy_decompress(&srcinfo); + jpeg_destroy_compress(&dstinfo); + fclose(input_file); + fclose(output_file); + return false; + } + + jpeg_create_decompress(&srcinfo); + jpeg_create_compress(&dstinfo); + + jpeg_stdio_src(&srcinfo, input_file); + jcopy_markers_setup(&srcinfo, copyoption); + + (void) jpeg_read_header(&srcinfo, true); + + jtransform_request_workspace(&srcinfo, &transformoption); + + // Read source file as DCT coefficients + src_coef_arrays = jpeg_read_coefficients(&srcinfo); + + // Initialize destination compression parameters from source values + jpeg_copy_critical_parameters(&srcinfo, &dstinfo); + + dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, + src_coef_arrays, &transformoption); + + // Specify data destination for compression + jpeg_stdio_dest(&dstinfo, output_file); + + // Start compressor (note no image data is actually written here) + jpeg_write_coefficients(&dstinfo, dst_coef_arrays); + + // Copy to the output file any extra markers that we want to preserve + jcopy_markers_execute(&srcinfo, &dstinfo, copyoption); + + jtransform_execute_transformation(&srcinfo, &dstinfo, + src_coef_arrays, &transformoption); + + // Finish compression and release memory + jpeg_finish_compress(&dstinfo); + jpeg_destroy_compress(&dstinfo); + (void) jpeg_finish_decompress(&srcinfo); + jpeg_destroy_decompress(&srcinfo); + + fclose(input_file); + fclose(output_file); + + // -- Metadata operations ------------------------------------------------------ + + // Reset the Exif orientation tag of the temp image to normal + DDebug() << "ExifRotate: set Orientation tag to normal: " << file << endl; + + metaData.load(temp); + metaData.setImageOrientation(DMetadata::ORIENTATION_NORMAL); + TQImage img(temp); + + // Get the new image dimension of the temp image. Using a dummy TQImage objet here + // has a sense because the Exif dimension information can be missing from original image. + // Get new dimensions with TQImage will always work... + metaData.setImageDimensions(img.size()); + + // Update the image thumbnail. + TQImage thumb = img.scale(160, 120, TQImage::ScaleMin); + metaData.setExifThumbnail(thumb); + + // Update Exif Document Name tag (the orinal file name from camera for example). + metaData.setExifTagString("Exif.Image.DocumentName", documentName); + + // We update all new metadata now... + metaData.applyChanges(); + + // ----------------------------------------------------------------------------- + // set the file modification time of the temp file to that + // of the original file + struct stat st; + stat(in, &st); + + struct utimbuf ut; + ut.modtime = st.st_mtime; + ut.actime = st.st_atime; + + utime(out, &ut); + + // now overwrite the original file + if (rename(out, in) == 0) + { + return true; + } + else + { + // moving failed. unlink the temp file + unlink(out); + return false; + } + } + + // Not a jpeg image. + DDebug() << "ExifRotate: not a JPEG file: " << file << endl; + return false; +} + +bool jpegConvert(const TQString& src, const TQString& dest, const TQString& documentName, const TQString& format) +{ + TQFileInfo fi(src); + if (!fi.exists()) + { + DDebug() << "JpegConvert: file do not exist: " << src << endl; + return false; + } + + if (isJpegImage(src)) + { + DImg image(src); + + // Get image Exif/Iptc data. + DMetadata meta; + meta.setExif(image.getExif()); + meta.setIptc(image.getIptc()); + + // Update Iptc preview. + TQImage preview = image.smoothScale(1280, 1024, TQSize::ScaleMin).copyTQImage(); + + // TODO: see B.K.O #130525. a JPEG segment is limited to 64K. If the IPTC byte array is + // bigger than 64K duing of image preview tag size, the target JPEG image will be + // broken. Note that IPTC image preview tag is limited to 256K!!! + // Temp. solution to disable IPTC preview record in JPEG file until a right solution + // will be found into Exiv2. + // Note : There is no limitation with TIFF and PNG about IPTC byte array size. + + if (format.upper() != TQString("JPG") && format.upper() != TQString("JPEG") && + format.upper() != TQString("JPE")) + meta.setImagePreview(preview); + + // Update Exif thumbnail. + TQImage thumb = preview.smoothScale(160, 120, TQImage::ScaleMin); + meta.setExifThumbnail(thumb); + + // Update Exif Document Name tag (the orinal file name from camera for example). + meta.setExifTagString("Exif.Image.DocumentName", documentName); + + // Store new Exif/Iptc data into image. + image.setExif(meta.getExif()); + image.setIptc(meta.getIptc()); + + // And now save the image to a new file format. + + if ( format.upper() == TQString("PNG") ) + image.setAttribute("quality", 9); + + if ( format.upper() == TQString("TIFF") || format.upper() == TQString("TIF") ) + image.setAttribute("compress", true); + + return (image.save(dest, format)); + } + + return false; +} + +bool isJpegImage(const TQString& file) +{ + // Check if the file is an JPEG image + TQString format = TQString(TQImage::imageFormat(file)).upper(); + DDebug() << "mimetype = " << format << endl; + if (format !="JPEG") return false; + + return true; +} + +} // Namespace Digikam |