/*
* 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 <kapplication.h>
# include <kmessagebox.h>
# include <klocale.h>
# include <KoDocumentInfo.h>
# include <kio/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"