/* ============================================================ * * This file is a part of kipi-plugins project * http://www.kipi-plugins.org * * Date : 2004-06-08 * Description : Loss less JPEG files transformations. * * Copyright (C) 2004 by Ralf Hoelzer * Copyright (C) 2004-2005 by Marcel Wiesweg * Copyright (C) 2006-2007 by Gilles Caulier * * NOTE: Do not use kdDebug() in this implementation because * it will be multithreaded. Use tqDebug() instead. * See B.K.O #133026 for details. * * 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. * * ============================================================ */ // C++ includes. #include #include // C Ansi includes. extern "C" { #include #include #include #include } // TQt includes. #include #include #include // KDE includes. #include #include // Local includes. #include "pluginsversion.h" #include "transupp.h" #include "jpegtransform.h" namespace KIPIJPEGLossLessPlugin { const Matrix Matrix::none ( 1, 0, 0, 1); const Matrix Matrix::rotate90 ( 0, -1, 1, 0); const Matrix Matrix::rotate180 (-1, 0, 0, -1); const Matrix Matrix::rotate270 ( 0, 1, -1, 0); const Matrix Matrix::flipHorizontal (-1, 0, 0, 1); const Matrix Matrix::flipVertical ( 1, 0, 0, -1); const Matrix Matrix::rotate90flipHorizontal ( 0, 1, 1, 0); const Matrix Matrix::rotate90flipVertical ( 0, -1, -1, 0); // To manage Errors/Warnings handling provide by libjpeg //#define ENABLE_DEBUG_MESSAGES struct jpegtransform_jpeg_error_mgr : public jpeg_error_mgr { jmp_buf setjmp_buffer; }; static void jpegtransform_jpeg_error_exit(j_common_ptr cinfo); static void jpegtransform_jpeg_emit_message(j_common_ptr cinfo, int msg_level); static void jpegtransform_jpeg_output_message(j_common_ptr cinfo); static void jpegtransform_jpeg_error_exit(j_common_ptr cinfo) { jpegtransform_jpeg_error_mgr* myerr = (jpegtransform_jpeg_error_mgr*) cinfo->err; char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); #ifdef ENABLE_DEBUG_MESSAGES tqDebug("%s", buffer) #endif longjmp(myerr->setjmp_buffer, 1); } static void jpegtransform_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 tqDebug("%s (%i)", buffer, msg_level); #endif } static void jpegtransform_jpeg_output_message(j_common_ptr cinfo) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); #ifdef ENABLE_DEBUG_MESSAGES tqDebug("%s", buffer) #endif } bool transformJPEG(const TQString& src, const TQString& destGiven, Matrix &userAction, TQString& err) { //may be modified TQString dest(destGiven); JCOPY_OPTION copyoption = JCOPYOPT_ALL; jpeg_transform_info transformoption; memset(&transformoption, 0, sizeof(jpeg_transform_info)); transformoption.force_grayscale = false; transformoption.trim = false; struct jpeg_decompress_struct srcinfo; struct jpeg_compress_struct dstinfo; struct jpegtransform_jpeg_error_mgr jsrcerr, jdsterr; jvirt_barray_ptr * src_coef_arrays; jvirt_barray_ptr * dst_coef_arrays; Matrix exifAction, action; JXFORM_CODE flip, rotate; // Initialize the JPEG decompression object with default error handling srcinfo.err = jpeg_std_error(&jsrcerr); srcinfo.err->error_exit = jpegtransform_jpeg_error_exit; srcinfo.err->emit_message = jpegtransform_jpeg_emit_message; srcinfo.err->output_message = jpegtransform_jpeg_output_message; // Initialize the JPEG compression object with default error handling dstinfo.err = jpeg_std_error(&jdsterr); dstinfo.err->error_exit = jpegtransform_jpeg_error_exit; dstinfo.err->emit_message = jpegtransform_jpeg_emit_message; dstinfo.err->output_message = jpegtransform_jpeg_output_message; FILE *input_file; FILE *output_file; input_file = fopen(TQFile::encodeName(src), "rb"); if (!input_file) { tqDebug("ImageRotate/ImageFlip: Error in opening input file"); err = i18n("Error in opening input file"); return false; } output_file = fopen(TQFile::encodeName(dest), "wb"); if (!output_file) { fclose(input_file); tqDebug("ImageRotate/ImageFlip: Error in opening output file"); err = i18n("Error in opening output file"); 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); // Get Exif orientation action to do. KExiv2Iface::KExiv2 exiv2Iface; exiv2Iface.load(src); getExifAction(exifAction, exiv2Iface.getImageOrientation()); // Compose actions: first exif, then user action*=exifAction; action*=userAction; // Convert action into flip+rotate action convertTransform(action, flip, rotate); tqDebug("Transforming with option %i %i", flip, rotate); if (flip == JXFORM_NONE && rotate == JXFORM_NONE) { err = "nothing to do"; // magic string fclose(output_file); fclose(input_file); return false; } bool twoPass = (flip != JXFORM_NONE); // If twoPass is true, we need another file (src -> tempFile -> destGiven) if (twoPass) { KTempFile tempFile; tempFile.setAutoDelete(false); dest=tempFile.name(); } output_file = fopen(TQFile::encodeName(dest), "wb"); if (!output_file) { fclose(input_file); tqDebug("ImageRotate/ImageFlip: Error in opening output file"); err = i18n("Error in opening output file"); return false; } // First rotate - execute even if rotate is JXFORM_NONE to apply new EXIF settings transformoption.transform=rotate; 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); // Do not write a JFIF header if previously the image did not contain it dstinfo.write_JFIF_header = false; // 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); // Flip if needed if (twoPass) { // Initialize the JPEG decompression object with default error handling srcinfo.err = jpeg_std_error(&jsrcerr); jpeg_create_decompress(&srcinfo); // Initialize the JPEG compression object with default error handling dstinfo.err = jpeg_std_error(&jdsterr); jpeg_create_compress(&dstinfo); input_file = fopen(TQFile::encodeName(dest), "rb"); if (!input_file) { tqDebug("ImageRotate/ImageFlip: Error in opening input file"); err = i18n("Error in opening input file"); return false; } output_file = fopen(TQFile::encodeName(destGiven), "wb"); if (!output_file) { fclose(input_file); tqDebug("ImageRotate/ImageFlip: Error in opening output file"); err = i18n("Error in opening output file"); return false; } jpeg_stdio_src(&srcinfo, input_file); jcopy_markers_setup(&srcinfo, copyoption); (void) jpeg_read_header(&srcinfo, true); transformoption.transform=flip; 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); // Do not write a JFIF header if previously the image did not contain it dstinfo.write_JFIF_header = false; // 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); // Unlink temp file unlink(TQFile::encodeName(dest)); } // And set finaly update the metadata to target file. TQImage img(destGiven); TQImage exifThumbnail = img.scale(160, 120, TQ_ScaleMin); exiv2Iface.load(destGiven); exiv2Iface.setImageOrientation(KExiv2Iface::KExiv2::ORIENTATION_NORMAL); exiv2Iface.setImageProgramId(TQString("Kipi-plugins"), TQString(kipiplugins_version)); exiv2Iface.setImageDimensions(img.size()); exiv2Iface.setExifThumbnail(exifThumbnail); exiv2Iface.save(destGiven); return true; } /** Converts the mathematically correct description into the primitive operations that can be carried out losslessly. */ void convertTransform(Matrix &action, JXFORM_CODE &flip, JXFORM_CODE &rotate) { flip = JXFORM_NONE; rotate = JXFORM_NONE; if (action == Matrix::rotate90) { rotate = JXFORM_ROT_90; } else if (action == Matrix::rotate180) { rotate = JXFORM_ROT_180; } else if (action == Matrix::rotate270) { rotate = JXFORM_ROT_270; } else if (action == Matrix::flipHorizontal) { flip = JXFORM_FLIP_H; } else if (action == Matrix::flipVertical) { flip = JXFORM_FLIP_V; } else if (action == Matrix::rotate90flipHorizontal) { //first rotate, then flip! rotate = JXFORM_ROT_90; flip = JXFORM_FLIP_H; } else if (action == Matrix::rotate90flipVertical) { //first rotate, then flip! rotate = JXFORM_ROT_90; flip = JXFORM_FLIP_V; } } void getExifAction(Matrix &action, KExiv2Iface::KExiv2::ImageOrientation exifOrientation) { switch (exifOrientation) { case KExiv2Iface::KExiv2::ORIENTATION_NORMAL: break; case KExiv2Iface::KExiv2::ORIENTATION_HFLIP: action*=Matrix::flipHorizontal; break; case KExiv2Iface::KExiv2::ORIENTATION_ROT_180: action*=Matrix::rotate180; break; case KExiv2Iface::KExiv2::ORIENTATION_VFLIP: action*=Matrix::flipVertical; break; case KExiv2Iface::KExiv2::ORIENTATION_ROT_90_HFLIP: action*=Matrix::rotate90flipHorizontal; break; case KExiv2Iface::KExiv2::ORIENTATION_ROT_90: action*=Matrix::rotate90; break; case KExiv2Iface::KExiv2::ORIENTATION_ROT_90_VFLIP: action*=Matrix::rotate90flipVertical; break; case KExiv2Iface::KExiv2::ORIENTATION_ROT_270: action*=Matrix::rotate270; break; case KExiv2Iface::KExiv2::ORIENTATION_UNSPECIFIED: action*=Matrix::none; break; } } } // NameSpace KIPIJPEGLossLessPlugin