diff options
Diffstat (limited to 'kipi-plugins/jpeglossless/jpegtransform.cpp')
-rw-r--r-- | kipi-plugins/jpeglossless/jpegtransform.cpp | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/kipi-plugins/jpeglossless/jpegtransform.cpp b/kipi-plugins/jpeglossless/jpegtransform.cpp new file mode 100644 index 0000000..7009e47 --- /dev/null +++ b/kipi-plugins/jpeglossless/jpegtransform.cpp @@ -0,0 +1,441 @@ +/* ============================================================ + * + * 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 <kde at ralfhoelzer.com> + * Copyright (C) 2004-2005 by Marcel Wiesweg <marcel.wiesweg@gmx.de> + * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com> + * + * NOTE: Do not use kdDebug() in this implementation because + * it will be multithreaded. Use qDebug() 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 <cstdio> +#include <cstdlib> + +// C Ansi includes. + +extern "C" +{ +#include <sys/types.h> +#include <unistd.h> +#include <setjmp.h> +#include <jpeglib.h> +} + +// Qt includes. + +#include <qstring.h> +#include <qwmatrix.h> +#include <qfile.h> + +// KDE includes. + +#include <klocale.h> +#include <ktempfile.h> + +// 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 + qDebug("%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 + qDebug("%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 + qDebug("%s", buffer) +#endif +} + +bool transformJPEG(const QString& src, const QString& destGiven, + Matrix &userAction, QString& err) +{ + //may be modified + QString dest(destGiven); + + JCOPY_OPTION copyoption = JCOPYOPT_ALL; + jpeg_transform_info transformoption; + + 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(QFile::encodeName(src), "rb"); + if (!input_file) + { + qDebug("ImageRotate/ImageFlip: Error in opening input file"); + err = i18n("Error in opening input file"); + return false; + } + + output_file = fopen(QFile::encodeName(dest), "wb"); + if (!output_file) + { + fclose(input_file); + qDebug("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); + qDebug("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(QFile::encodeName(dest), "wb"); + if (!output_file) + { + fclose(input_file); + qDebug("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(QFile::encodeName(dest), "rb"); + if (!input_file) + { + qDebug("ImageRotate/ImageFlip: Error in opening input file"); + err = i18n("Error in opening input file"); + return false; + } + + output_file = fopen(QFile::encodeName(destGiven), "wb"); + if (!output_file) + { + fclose(input_file); + qDebug("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(QFile::encodeName(dest)); + } + + // And set finaly update the metadata to target file. + + QImage img(destGiven); + QImage exifThumbnail = img.scale(160, 120, QImage::ScaleMin); + exiv2Iface.load(destGiven); + exiv2Iface.setImageOrientation(KExiv2Iface::KExiv2::ORIENTATION_NORMAL); + exiv2Iface.setImageProgramId(QString("Kipi-plugins"), QString(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 |