mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-23 18:32:50 +00:00
306 lines
9.5 KiB
C++
306 lines
9.5 KiB
C++
/* This file is part of the KDE libraries
|
|
Copyright (C) 2008 Andre Gemünd <scroogie@gmail.com>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library 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
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "jpegcreator.h"
|
|
|
|
#include <cstdio>
|
|
#include <csetjmp>
|
|
#include "jpegcreatorsettings.h"
|
|
#include <QCheckBox>
|
|
#include <QFile>
|
|
#include <QImage>
|
|
#include <kdemacros.h>
|
|
#include <klocale.h>
|
|
|
|
#ifdef HAVE_EXIV2
|
|
#include <exiv2/image.hpp>
|
|
#include <exiv2/exif.hpp>
|
|
#endif
|
|
|
|
extern "C"
|
|
{
|
|
#include <jpeglib.h>
|
|
|
|
KDE_EXPORT ThumbCreator *new_creator()
|
|
{
|
|
return new JpegCreator;
|
|
}
|
|
}
|
|
|
|
struct jpeg_custom_error_mgr
|
|
{
|
|
struct jpeg_error_mgr builtin;
|
|
jmp_buf setjmp_buffer;
|
|
};
|
|
|
|
void jpeg_custom_error_callback(j_common_ptr jpegDecompress)
|
|
{
|
|
jpeg_custom_error_mgr *custom_err = (jpeg_custom_error_mgr *)jpegDecompress->err;
|
|
|
|
// jump to error recovery (fallback to old method)
|
|
longjmp(custom_err->setjmp_buffer, 1);
|
|
}
|
|
|
|
#ifdef Q_CC_MSVC
|
|
typedef struct
|
|
{
|
|
struct jpeg_source_mgr pub;
|
|
JOCTET eoi[2];
|
|
} jpeg_custom_source_mgr;
|
|
|
|
void init_source (j_decompress_ptr cinfo)
|
|
{
|
|
}
|
|
|
|
boolean fill_input_buffer (j_decompress_ptr cinfo)
|
|
{
|
|
jpeg_custom_source_mgr* src = (jpeg_custom_source_mgr*) cinfo->src;
|
|
|
|
/* Create a fake EOI marker */
|
|
src->eoi[0] = (JOCTET) 0xFF;
|
|
src->eoi[1] = (JOCTET) JPEG_EOI;
|
|
src->pub.next_input_byte = src->eoi;
|
|
src->pub.bytes_in_buffer = 2;
|
|
|
|
return true;
|
|
}
|
|
|
|
void skip_input_data (j_decompress_ptr cinfo, long nbytes)
|
|
{
|
|
jpeg_custom_source_mgr* src = (jpeg_custom_source_mgr*) cinfo->src;
|
|
|
|
if (nbytes > 0)
|
|
{
|
|
while (nbytes > (long) src->pub.bytes_in_buffer)
|
|
{
|
|
nbytes -= (long) src->pub.bytes_in_buffer;
|
|
(void) fill_input_buffer(cinfo);
|
|
}
|
|
src->pub.next_input_byte += (size_t) nbytes;
|
|
src->pub.bytes_in_buffer -= (size_t) nbytes;
|
|
}
|
|
}
|
|
|
|
void term_source (j_decompress_ptr cinfo)
|
|
{
|
|
}
|
|
|
|
void jpeg_memory_src (j_decompress_ptr cinfo, const JOCTET * buffer, size_t bufsize)
|
|
{
|
|
jpeg_custom_source_mgr* src;
|
|
|
|
if (cinfo->src == NULL)
|
|
{
|
|
cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
|
|
sizeof(jpeg_custom_source_mgr));
|
|
}
|
|
|
|
src = (jpeg_custom_source_mgr*) cinfo->src;
|
|
src->pub.init_source = init_source;
|
|
src->pub.fill_input_buffer = fill_input_buffer;
|
|
src->pub.skip_input_data = skip_input_data;
|
|
src->pub.resync_to_restart = jpeg_resync_to_restart; // default
|
|
src->pub.term_source = term_source;
|
|
|
|
src->pub.next_input_byte = buffer;
|
|
src->pub.bytes_in_buffer = bufsize;
|
|
}
|
|
#endif
|
|
|
|
JpegCreator::JpegCreator()
|
|
{
|
|
}
|
|
|
|
QTransform JpegCreator::orientationMatrix(int exivOrientation) const
|
|
{
|
|
//Check (e.g.) man jpegexiforient for an explanation
|
|
switch (exivOrientation) {
|
|
case 2:
|
|
return QTransform(-1, 0, 0, 1, 0, 0);
|
|
case 3:
|
|
return QTransform(-1, 0, 0, -1, 0, 0);
|
|
case 4:
|
|
return QTransform(1, 0, 0, -1, 0, 0);
|
|
case 5:
|
|
return QTransform(0, 1, 1, 0, 0, 0);
|
|
case 6:
|
|
return QTransform(0, 1, -1, 0, 0, 0);
|
|
case 7:
|
|
return QTransform(0, -1, -1, 0, 0, 0);
|
|
case 8:
|
|
return QTransform(0, -1, 1, 0, 0, 0);
|
|
case 1:
|
|
default:
|
|
return QTransform(1, 0, 0, 1, 0, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is a faster thumbnail creation specifically for JPEG images, as it uses the libjpeg feature of
|
|
* calculating the inverse dct for a part of coefficients for lower resolutions.
|
|
* Interesting parameters are the quality settings of libjpeg
|
|
* jpegDecompress.do_fancy_upsampling (TRUE, FALSE)
|
|
* jpegDecompress.do_block_smoothing (TRUE, FALSE)
|
|
* jpegDecompress.dct_method (JDCT_IFAST, JDCT_ISLOW, JDCT_IFLOAT)
|
|
* and the resampling parameter of QImage.
|
|
*
|
|
* Important: We do not need to scaled to exact dimesions, as thumbnail.cpp will check dimensions and
|
|
* rescale anyway.
|
|
*/
|
|
bool JpegCreator::create(const QString &path, int width, int height, QImage &image)
|
|
{
|
|
QImage img;
|
|
const QByteArray name = QFile::encodeName(path);
|
|
FILE *jpegFile = fopen(name.constData(), "rb");
|
|
if (jpegFile == 0) {
|
|
return false;
|
|
}
|
|
|
|
// create jpeglib data structures and calculate scale denominator
|
|
struct jpeg_decompress_struct jpegDecompress;
|
|
struct jpeg_custom_error_mgr jpegError;
|
|
jpegDecompress.err = jpeg_std_error(&jpegError.builtin);
|
|
jpeg_create_decompress(&jpegDecompress);
|
|
#ifdef Q_CC_MSVC
|
|
QFile inFile(path);
|
|
QByteArray buf;
|
|
if(inFile.open(QIODevice::ReadOnly)) {
|
|
while(!inFile.atEnd()) {
|
|
buf += inFile.readLine();
|
|
}
|
|
inFile.close();
|
|
}
|
|
|
|
jpeg_memory_src(&jpegDecompress, (JOCTET*)buf.data(), buf.size());
|
|
#else
|
|
jpeg_stdio_src(&jpegDecompress, jpegFile);
|
|
#endif
|
|
jpeg_read_header(&jpegDecompress, TRUE);
|
|
|
|
const double ratioWidth = jpegDecompress.image_width / (double)width;
|
|
const double ratioHeight = jpegDecompress.image_height / (double)height;
|
|
int scale = 1;
|
|
if (ratioWidth > 7 || ratioHeight > 7) {
|
|
scale = 8;
|
|
} else if (ratioWidth > 3.5 || ratioHeight > 3.5) {
|
|
scale = 4;
|
|
} else if (ratioWidth > 1.75 || ratioHeight > 1.75) {
|
|
scale = 2;
|
|
}
|
|
|
|
// set jpeglib decompression parameters
|
|
jpegDecompress.scale_num = 1;
|
|
jpegDecompress.scale_denom = scale;
|
|
jpegDecompress.do_fancy_upsampling = FALSE;
|
|
jpegDecompress.do_block_smoothing = FALSE;
|
|
jpegDecompress.dct_method = JDCT_IFAST;
|
|
jpegDecompress.err->error_exit = jpeg_custom_error_callback;
|
|
jpegDecompress.out_color_space = JCS_RGB;
|
|
|
|
jpeg_calc_output_dimensions(&jpegDecompress);
|
|
|
|
if (setjmp(jpegError.setjmp_buffer)) {
|
|
jpeg_abort_decompress(&jpegDecompress);
|
|
fclose(jpegFile);
|
|
// libjpeg version failed, fall back to direct loading of QImage
|
|
if (!img.load(path)) {
|
|
return false;
|
|
}
|
|
if (img.depth() != 32) {
|
|
img = img.convertToFormat(QImage::Format_RGB32);
|
|
}
|
|
} else {
|
|
jpeg_start_decompress(&jpegDecompress);
|
|
img = QImage(jpegDecompress.output_width, jpegDecompress.output_height, QImage::Format_RGB32);
|
|
uchar *buffer = img.bits();
|
|
const int bpl = img.bytesPerLine();
|
|
while (jpegDecompress.output_scanline < jpegDecompress.output_height) {
|
|
// advance line-pointer to next line
|
|
uchar *line = buffer + jpegDecompress.output_scanline * bpl;
|
|
jpeg_read_scanlines(&jpegDecompress, &line, 1);
|
|
}
|
|
jpeg_finish_decompress(&jpegDecompress);
|
|
|
|
// align correctly for QImage
|
|
// code copied from Gwenview and digiKam
|
|
for (int i = 0; i < int(jpegDecompress.output_height); ++i) {
|
|
uchar *in = img.scanLine(i) + jpegDecompress.output_width * 3;
|
|
QRgb *out = (QRgb*)img.scanLine(i);
|
|
for (int j = jpegDecompress.output_width - 1; j >= 0; --j) {
|
|
in -= 3;
|
|
out[j] = qRgb(in[0], in[1], in[2]);
|
|
}
|
|
}
|
|
fclose(jpegFile);
|
|
jpeg_destroy_decompress(&jpegDecompress);
|
|
}
|
|
|
|
#ifdef HAVE_EXIV2
|
|
JpegCreatorSettings* settings = JpegCreatorSettings::self();
|
|
settings->readConfig();
|
|
if (settings->rotate()) {
|
|
//Handle exif rotation
|
|
try {
|
|
Exiv2::Image::AutoPtr exivImg = Exiv2::ImageFactory::open(name.constData());
|
|
if (exivImg.get()) {
|
|
exivImg->readMetadata();
|
|
Exiv2::ExifData exifData = exivImg->exifData();
|
|
if (!exifData.empty()) {
|
|
Exiv2::ExifKey key("Exif.Image.Orientation");
|
|
Exiv2::ExifData::iterator it = exifData.findKey(key);
|
|
if (it != exifData.end()) {
|
|
int orient = it->toLong();
|
|
image = img.transformed(orientationMatrix(orient));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
} catch (...) {
|
|
// Apparently libexiv changed its API at some point, a different exception is thrown
|
|
// depending on the version. an ifdef could make it work, but since we just ignore the exception
|
|
// there is no point in doing that
|
|
}
|
|
}
|
|
#endif
|
|
|
|
image = img;
|
|
return true;
|
|
}
|
|
|
|
ThumbCreator::Flags JpegCreator::flags() const
|
|
{
|
|
return None;
|
|
}
|
|
|
|
QWidget *JpegCreator::createConfigurationWidget()
|
|
{
|
|
QCheckBox *rotateCheckBox = new QCheckBox(i18nc("@option:check", "Rotate the image automatically"));
|
|
rotateCheckBox->setChecked(JpegCreatorSettings::rotate());
|
|
return rotateCheckBox;
|
|
}
|
|
|
|
void JpegCreator::writeConfiguration(const QWidget *configurationWidget)
|
|
{
|
|
const QCheckBox *rotateCheckBox = qobject_cast<const QCheckBox*>(configurationWidget);
|
|
if (rotateCheckBox) {
|
|
JpegCreatorSettings* settings = JpegCreatorSettings::self();
|
|
settings->setRotate(rotateCheckBox->isChecked());
|
|
settings->writeConfig();
|
|
}
|
|
}
|