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.
digikam/digikam/libs/jpegutils/jpegutils.cpp

533 lines
16 KiB

/* ============================================================
*
* 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 <renchi@pooh.tam.uiuc.edu>
* 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