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.
543 lines
18 KiB
543 lines
18 KiB
/*
|
|
* Copyright (c) 2005 Cyrille Berger <cberger@cberger.net>
|
|
*
|
|
* 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 of the License, 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "kis_jpeg_converter.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
extern "C" {
|
|
#include <iccjpeg.h>
|
|
}
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <tdeapplication.h>
|
|
#include <tdemessagebox.h>
|
|
#include <tdelocale.h>
|
|
|
|
#include <KoDocumentInfo.h>
|
|
|
|
#include <tdeio/netaccess.h>
|
|
|
|
#include <kis_abstract_colorspace.h>
|
|
#include <kis_colorspace_factory_registry.h>
|
|
#include <kis_doc.h>
|
|
#include <kis_image.h>
|
|
#include <kis_iterators_pixel.h>
|
|
#include <kis_paint_layer.h>
|
|
#include <kis_group_layer.h>
|
|
#include <kis_meta_registry.h>
|
|
#include <kis_profile.h>
|
|
|
|
#include <kis_exif_io.h>
|
|
|
|
extern "C" {
|
|
#include <libexif/exif-loader.h>
|
|
#include <libexif/exif-utils.h>
|
|
}
|
|
|
|
#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
|
|
#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
|
|
#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
|
|
#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
|
|
|
|
namespace {
|
|
|
|
J_COLOR_SPACE getColorTypeforColorSpace( KisColorSpace * cs)
|
|
{
|
|
if ( cs->id() == KisID("GRAYA") || cs->id() == KisID("GRAYA16") )
|
|
{
|
|
return JCS_GRAYSCALE;
|
|
}
|
|
if ( cs->id() == KisID("RGBA") || cs->id() == KisID("RGBA16") )
|
|
{
|
|
return JCS_RGB;
|
|
}
|
|
if ( cs->id() == KisID("CMYK") || cs->id() == KisID("CMYK16") )
|
|
{
|
|
return JCS_CMYK;
|
|
}
|
|
KMessageBox::error(0, i18n("Cannot export images in %1.\n").arg(cs->id().name()) ) ;
|
|
return JCS_UNKNOWN;
|
|
}
|
|
|
|
TQString getColorSpaceForColorType(J_COLOR_SPACE color_type) {
|
|
kdDebug(41008) << "color_type = " << color_type << endl;
|
|
if(color_type == JCS_GRAYSCALE)
|
|
{
|
|
return "GRAYA";
|
|
} else if(color_type == JCS_RGB) {
|
|
return "RGBA";
|
|
} else if(color_type == JCS_CMYK) {
|
|
return "CMYK";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
}
|
|
|
|
KisJPEGConverter::KisJPEGConverter(KisDoc *doc, KisUndoAdapter *adapter)
|
|
{
|
|
m_doc = doc;
|
|
m_adapter = adapter;
|
|
m_job = 0;
|
|
m_stop = false;
|
|
}
|
|
|
|
KisJPEGConverter::~KisJPEGConverter()
|
|
{
|
|
}
|
|
|
|
KisImageBuilder_Result KisJPEGConverter::decode(const KURL& uri)
|
|
{
|
|
struct jpeg_decompress_struct cinfo;
|
|
struct jpeg_error_mgr jerr;
|
|
|
|
cinfo.err = jpeg_std_error(&jerr);
|
|
jpeg_create_decompress(&cinfo);
|
|
|
|
// open the file
|
|
FILE *fp = fopen(TQFile::encodeName(uri.path()), "rb");
|
|
if (!fp)
|
|
{
|
|
return (KisImageBuilder_RESULT_NOT_EXIST);
|
|
}
|
|
jpeg_stdio_src(&cinfo, fp);
|
|
|
|
jpeg_save_markers (&cinfo, JPEG_COM, 0xFFFF);
|
|
/* Save APP0..APP15 markers */
|
|
for (int m = 0; m < 16; m++)
|
|
jpeg_save_markers (&cinfo, JPEG_APP0 + m, 0xFFFF);
|
|
|
|
|
|
// setup_read_icc_profile(&cinfo);
|
|
// read header
|
|
jpeg_read_header(&cinfo, true);
|
|
|
|
// start reading
|
|
jpeg_start_decompress(&cinfo);
|
|
|
|
// Get the colorspace
|
|
TQString csName = getColorSpaceForColorType(cinfo.out_color_space);
|
|
if(csName.isEmpty()) {
|
|
kdDebug(41008) << "unsupported colorspace : " << cinfo.out_color_space << endl;
|
|
jpeg_destroy_decompress(&cinfo);
|
|
fclose(fp);
|
|
return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
|
|
}
|
|
uchar* profile_data;
|
|
uint profile_len;
|
|
KisProfile* profile = 0;
|
|
TQByteArray profile_rawdata;
|
|
if( read_icc_profile (&cinfo, &profile_data, &profile_len))
|
|
{
|
|
profile_rawdata.resize(profile_len);
|
|
memcpy(profile_rawdata.data(), profile_data, profile_len);
|
|
cmsHPROFILE hProfile = cmsOpenProfileFromMem(profile_data, (DWORD)profile_len);
|
|
|
|
if (hProfile != (cmsHPROFILE) NULL) {
|
|
profile = new KisProfile( profile_rawdata);
|
|
TQ_CHECK_PTR(profile);
|
|
kdDebug(41008) << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo() << endl;
|
|
if(!profile->isSuitableForOutput())
|
|
{
|
|
kdDebug(41008) << "the profile is not suitable for output and therefore cannot be used in chalk, we need to convert the image to a standard profile" << endl; // TODO: in ko2 popup a selection menu to inform the user
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retrieve a pointer to the colorspace
|
|
KisColorSpace* cs;
|
|
if (profile && profile->isSuitableForOutput())
|
|
{
|
|
kdDebug(41008) << "image has embedded profile: " << profile -> productName() << "\n";
|
|
cs = KisMetaRegistry::instance()->csRegistry()->getColorSpace(csName, profile);
|
|
}
|
|
else
|
|
cs = KisMetaRegistry::instance()->csRegistry()->getColorSpace(KisID(csName,""),"");
|
|
|
|
if(cs == 0)
|
|
{
|
|
kdDebug(41008) << "unknown colorspace" << endl;
|
|
jpeg_destroy_decompress(&cinfo);
|
|
fclose(fp);
|
|
return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
|
|
}
|
|
|
|
// Create the cmsTransform if needed
|
|
|
|
cmsHTRANSFORM transform = 0;
|
|
if(profile && !profile->isSuitableForOutput())
|
|
{
|
|
transform = cmsCreateTransform(profile->profile(), cs->colorSpaceType(),
|
|
cs->getProfile()->profile() , cs->colorSpaceType(),
|
|
INTENT_PERCEPTUAL, 0);
|
|
}
|
|
|
|
// Creating the KisImageSP
|
|
if( ! m_img) {
|
|
m_img = new KisImage(m_doc->undoAdapter(), cinfo.image_width, cinfo.image_height, cs, "built image");
|
|
TQ_CHECK_PTR(m_img);
|
|
if(profile && !profile->isSuitableForOutput())
|
|
{
|
|
m_img -> addAnnotation( new KisAnnotation( profile->productName(), "", profile_rawdata) );
|
|
}
|
|
}
|
|
|
|
KisPaintLayerSP layer = new KisPaintLayer(m_img, m_img -> nextLayerName(), TQ_UINT8_MAX);
|
|
|
|
// Read exif information if any
|
|
|
|
// Read data
|
|
JSAMPROW row_pointer = new JSAMPLE[cinfo.image_width *cinfo.num_components];
|
|
|
|
for (; cinfo.output_scanline < cinfo.image_height;) {
|
|
KisHLineIterator it = layer->paintDevice()->createHLineIterator(0, cinfo.output_scanline, cinfo.image_width, true);
|
|
jpeg_read_scanlines(&cinfo, &row_pointer, 1);
|
|
TQ_UINT8 *src = row_pointer;
|
|
switch(cinfo.out_color_space)
|
|
{
|
|
case JCS_GRAYSCALE:
|
|
while (!it.isDone()) {
|
|
TQ_UINT8 *d = it.rawData();
|
|
d[0] = *(src++);
|
|
if(transform) cmsDoTransform(transform, d, d, 1);
|
|
d[1] = TQ_UINT8_MAX;
|
|
++it;
|
|
}
|
|
break;
|
|
case JCS_RGB:
|
|
while (!it.isDone()) {
|
|
TQ_UINT8 *d = it.rawData();
|
|
d[2] = *(src++);
|
|
d[1] = *(src++);
|
|
d[0] = *(src++);
|
|
if(transform) cmsDoTransform(transform, d, d, 1);
|
|
d[3] = TQ_UINT8_MAX;
|
|
++it;
|
|
}
|
|
break;
|
|
case JCS_CMYK:
|
|
while (!it.isDone()) {
|
|
TQ_UINT8 *d = it.rawData();
|
|
d[0] = TQ_UINT8_MAX - *(src++);
|
|
d[1] = TQ_UINT8_MAX - *(src++);
|
|
d[2] = TQ_UINT8_MAX - *(src++);
|
|
d[3] = TQ_UINT8_MAX - *(src++);
|
|
if(transform) cmsDoTransform(transform, d, d, 1);
|
|
d[4] = TQ_UINT8_MAX;
|
|
++it;
|
|
}
|
|
break;
|
|
default:
|
|
return KisImageBuilder_RESULT_UNSUPPORTED;
|
|
}
|
|
}
|
|
|
|
m_img->addLayer(layer.data(), m_img->rootLayer(), 0);
|
|
|
|
// Read exif informations
|
|
|
|
kdDebug(41008) << "Looking for exif information" << endl;
|
|
|
|
for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != NULL; marker = marker->next) {
|
|
kdDebug(41008) << "Marker is " << marker->marker << endl;
|
|
if (marker->marker != (JOCTET) (JPEG_APP0 + 1) ||
|
|
marker->data_length < 14)
|
|
continue; /* Exif data is in an APP1 marker of at least 14 octets */
|
|
|
|
if (GETJOCTET (marker->data[0]) != (JOCTET) 0x45 ||
|
|
GETJOCTET (marker->data[1]) != (JOCTET) 0x78 ||
|
|
GETJOCTET (marker->data[2]) != (JOCTET) 0x69 ||
|
|
GETJOCTET (marker->data[3]) != (JOCTET) 0x66 ||
|
|
GETJOCTET (marker->data[4]) != (JOCTET) 0x00 ||
|
|
GETJOCTET (marker->data[5]) != (JOCTET) 0x00)
|
|
continue; /* no Exif header */
|
|
kdDebug(41008) << "Found exif information of length : "<< marker->data_length << endl;
|
|
KisExifIO exifIO(layer->paintDevice()->exifInfo());
|
|
exifIO.readExifFromMem( marker->data , marker->data_length );
|
|
// Interpret orientation tag
|
|
ExifValue v;
|
|
if( layer->paintDevice()->exifInfo()->getValue("Orientation", v) && v.type() == ExifValue::EXIF_TYPE_SHORT)
|
|
{
|
|
switch(v.asShort(0)) //
|
|
{
|
|
case 2:
|
|
layer->paintDevice()->mirrorY();
|
|
break;
|
|
case 3:
|
|
image()->rotate(M_PI, 0);
|
|
break;
|
|
case 4:
|
|
layer->paintDevice()->mirrorX();
|
|
break;
|
|
case 5:
|
|
image()->rotate(M_PI/2, 0);
|
|
layer->paintDevice()->mirrorY();
|
|
break;
|
|
case 6:
|
|
image()->rotate(M_PI/2, 0);
|
|
break;
|
|
case 7:
|
|
image()->rotate(M_PI/2, 0);
|
|
layer->paintDevice()->mirrorX();
|
|
break;
|
|
case 8:
|
|
image()->rotate(-M_PI/2 + 2*M_PI, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
v.setValue(0, (TQ_UINT16)1);
|
|
layer->paintDevice()->exifInfo()->setValue("Orientation", v);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Finish decompression
|
|
jpeg_finish_decompress(&cinfo);
|
|
jpeg_destroy_decompress(&cinfo);
|
|
fclose(fp);
|
|
delete []row_pointer;
|
|
return KisImageBuilder_RESULT_OK;
|
|
}
|
|
|
|
|
|
|
|
KisImageBuilder_Result KisJPEGConverter::buildImage(const KURL& uri)
|
|
{
|
|
if (uri.isEmpty())
|
|
return KisImageBuilder_RESULT_NO_URI;
|
|
|
|
if (!TDEIO::NetAccess::exists(uri, false, tqApp -> mainWidget())) {
|
|
return KisImageBuilder_RESULT_NOT_EXIST;
|
|
}
|
|
|
|
// We're not set up to handle asynchronous loading at the moment.
|
|
KisImageBuilder_Result result = KisImageBuilder_RESULT_FAILURE;
|
|
TQString tmpFile;
|
|
|
|
if (TDEIO::NetAccess::download(uri, tmpFile, tqApp -> mainWidget())) {
|
|
KURL uriTF;
|
|
uriTF.setPath( tmpFile );
|
|
result = decode(uriTF);
|
|
TDEIO::NetAccess::removeTempFile(tmpFile);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
KisImageSP KisJPEGConverter::image()
|
|
{
|
|
return m_img;
|
|
}
|
|
|
|
|
|
KisImageBuilder_Result KisJPEGConverter::buildFile(const KURL& uri, KisPaintLayerSP layer, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisJPEGOptions options, KisExifInfo* exifInfo)
|
|
{
|
|
if (!layer)
|
|
return KisImageBuilder_RESULT_INVALID_ARG;
|
|
|
|
KisImageSP img = layer -> image();
|
|
if (!img)
|
|
return KisImageBuilder_RESULT_EMPTY;
|
|
|
|
if (uri.isEmpty())
|
|
return KisImageBuilder_RESULT_NO_URI;
|
|
|
|
if (!uri.isLocalFile())
|
|
return KisImageBuilder_RESULT_NOT_LOCAL;
|
|
// Open file for writing
|
|
FILE *fp = fopen(TQFile::encodeName(uri.path()), "wb");
|
|
if (!fp)
|
|
{
|
|
return (KisImageBuilder_RESULT_FAILURE);
|
|
}
|
|
uint height = img->height();
|
|
uint width = img->width();
|
|
// Initialize structure
|
|
struct jpeg_compress_struct cinfo;
|
|
jpeg_create_compress(&cinfo);
|
|
// Initialize error output
|
|
struct jpeg_error_mgr jerr;
|
|
cinfo.err = jpeg_std_error(&jerr);
|
|
// Initialize output stream
|
|
jpeg_stdio_dest(&cinfo, fp);
|
|
|
|
cinfo.image_width = width; // image width and height, in pixels
|
|
cinfo.image_height = height;
|
|
cinfo.input_components = img->colorSpace()->nColorChannels(); // number of color channels per pixel */
|
|
J_COLOR_SPACE color_type = getColorTypeforColorSpace(img->colorSpace());
|
|
if(color_type == JCS_UNKNOWN)
|
|
{
|
|
TDEIO::del(uri);
|
|
return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
|
|
}
|
|
cinfo.in_color_space = color_type; // colorspace of input image
|
|
|
|
|
|
// Set default compression parameters
|
|
jpeg_set_defaults(&cinfo);
|
|
// Customize them
|
|
jpeg_set_quality(&cinfo, options.quality, true);
|
|
|
|
if(options.progressive)
|
|
{
|
|
jpeg_simple_progression (&cinfo);
|
|
}
|
|
|
|
// Start compression
|
|
jpeg_start_compress(&cinfo, true);
|
|
// Save exif information if any available
|
|
if(exifInfo)
|
|
{
|
|
kdDebug(41008) << "Trying to save exif information" << endl;
|
|
KisExifIO exifIO(exifInfo);
|
|
unsigned char* exif_data;
|
|
unsigned int exif_size;
|
|
exifIO.saveExifToMem( &exif_data, &exif_size);
|
|
kdDebug(41008) << "Exif informations size is " << exif_size << endl;
|
|
if (exif_size < MAX_DATA_BYTES_IN_MARKER)
|
|
{
|
|
jpeg_write_marker(&cinfo, JPEG_APP0 + 1, exif_data, exif_size);
|
|
} else {
|
|
kdDebug(41008) << "exif informations couldn't be saved." << endl;
|
|
}
|
|
}
|
|
|
|
|
|
// Save annotation
|
|
vKisAnnotationSP_it it = annotationsStart;
|
|
while(it != annotationsEnd) {
|
|
if (!(*it) || (*it) -> type() == TQString()) {
|
|
kdDebug(41008) << "Warning: empty annotation" << endl;
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
kdDebug(41008) << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size() << endl;
|
|
|
|
if ((*it) -> type().startsWith("chalk_attribute:")) { // Attribute
|
|
// FIXME
|
|
kdDebug(41008) << "can't save this annotation : " << (*it) -> type() << endl;
|
|
} else { // Profile
|
|
//char* name = new char[(*it)->type().length()+1];
|
|
write_icc_profile(& cinfo, (uchar*)(*it)->annotation().data(), (*it)->annotation().size());
|
|
}
|
|
++it;
|
|
}
|
|
|
|
|
|
// Write data information
|
|
|
|
JSAMPROW row_pointer = new JSAMPLE[width*cinfo.input_components];
|
|
int color_nb_bits = 8 * layer->paintDevice()->pixelSize() / layer->paintDevice()->nChannels();
|
|
|
|
for (; cinfo.next_scanline < height;) {
|
|
KisHLineIterator it = layer->paintDevice()->createHLineIterator(0, cinfo.next_scanline, width, false);
|
|
TQ_UINT8 *dst = row_pointer;
|
|
switch(color_type)
|
|
{
|
|
case JCS_GRAYSCALE:
|
|
if(color_nb_bits == 16)
|
|
{
|
|
while (!it.isDone()) {
|
|
const TQ_UINT16 *d = reinterpret_cast<const TQ_UINT16 *>(it.rawData());
|
|
*(dst++) = d[0] / TQ_UINT8_MAX;
|
|
++it;
|
|
}
|
|
} else {
|
|
while (!it.isDone()) {
|
|
const TQ_UINT8 *d = it.rawData();
|
|
*(dst++) = d[0];
|
|
++it;
|
|
}
|
|
}
|
|
break;
|
|
case JCS_RGB:
|
|
if(color_nb_bits == 16)
|
|
{
|
|
while (!it.isDone()) {
|
|
const TQ_UINT16 *d = reinterpret_cast<const TQ_UINT16 *>(it.rawData());
|
|
*(dst++) = d[2] / TQ_UINT8_MAX;
|
|
*(dst++) = d[1] / TQ_UINT8_MAX;
|
|
*(dst++) = d[0] / TQ_UINT8_MAX;
|
|
++it;
|
|
}
|
|
} else {
|
|
while (!it.isDone()) {
|
|
const TQ_UINT8 *d = it.rawData();
|
|
*(dst++) = d[2];
|
|
*(dst++) = d[1];
|
|
*(dst++) = d[0];
|
|
++it;
|
|
}
|
|
}
|
|
break;
|
|
case JCS_CMYK:
|
|
if(color_nb_bits == 16)
|
|
{
|
|
while (!it.isDone()) {
|
|
const TQ_UINT16 *d = reinterpret_cast<const TQ_UINT16 *>(it.rawData());
|
|
*(dst++) = TQ_UINT8_MAX - d[0] / TQ_UINT8_MAX;
|
|
*(dst++) = TQ_UINT8_MAX - d[1] / TQ_UINT8_MAX;
|
|
*(dst++) = TQ_UINT8_MAX - d[2] / TQ_UINT8_MAX;
|
|
*(dst++) = TQ_UINT8_MAX - d[3] / TQ_UINT8_MAX;
|
|
++it;
|
|
}
|
|
} else {
|
|
while (!it.isDone()) {
|
|
const TQ_UINT8 *d = it.rawData();
|
|
*(dst++) = TQ_UINT8_MAX - d[0];
|
|
*(dst++) = TQ_UINT8_MAX - d[1];
|
|
*(dst++) = TQ_UINT8_MAX - d[2];
|
|
*(dst++) = TQ_UINT8_MAX - d[3];
|
|
++it;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
TDEIO::del(uri);
|
|
return KisImageBuilder_RESULT_UNSUPPORTED;
|
|
}
|
|
jpeg_write_scanlines(&cinfo, &row_pointer, 1);
|
|
}
|
|
|
|
|
|
// Writting is over
|
|
jpeg_finish_compress(&cinfo);
|
|
fclose(fp);
|
|
|
|
delete [] row_pointer;
|
|
// Free memory
|
|
jpeg_destroy_compress(&cinfo);
|
|
|
|
return KisImageBuilder_RESULT_OK;
|
|
}
|
|
|
|
|
|
void KisJPEGConverter::cancel()
|
|
{
|
|
m_stop = true;
|
|
}
|
|
|
|
#include "kis_jpeg_converter.moc"
|
|
|