You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
13 KiB
443 lines
13 KiB
/* ============================================================
|
|
*
|
|
* 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 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 <cstdio>
|
|
#include <cstdlib>
|
|
|
|
// C Ansi includes.
|
|
|
|
extern "C"
|
|
{
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <setjmp.h>
|
|
#include <jpeglib.h>
|
|
}
|
|
|
|
// TQt includes.
|
|
|
|
#include <tqstring.h>
|
|
#include <tqwmatrix.h>
|
|
#include <tqfile.h>
|
|
|
|
// KDE includes.
|
|
|
|
#include <tdelocale.h>
|
|
#include <tdetempfile.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
|
|
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
|