/* ============================================================ * * 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 * Copyright (C) 2006-2009 by Gilles Caulier * * 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 #include // C Ansi includes. extern "C" { #include #include #include #include #include #include } // TQt includes. #include #include #include // 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; jerror_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