/*
 * imbuf_coca.m
 *
 * $Id$
 * 
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * 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.
 *
 * Contributor(s): Damien Plisson 10/2009
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/**
 * Provides image file loading and saving for Blender, via Cocoa.
 *
 */

#include <stdint.h>
#include <string.h>
#import <Cocoa/Cocoa.h>

#include "imbuf.h"

#include "IMB_cocoa.h"

#include "BKE_global.h"
#include "BKE_colortools.h"

#include "IMB_imbuf_types.h"
#include "IMB_imbuf.h"

#include "IMB_allocimbuf.h"



#pragma mark load/save functions

/**
 * Loads an image from the supplied buffer
 *
 * Loads any Core Graphics supported type
 * Currently is : TIFF, BMP, JPEG, GIF, PNG, DIB, ICO, and various RAW formats
 *
 * @param mem:   Memory containing the bitmap image
 * @param size:  Size of the mem buffer.
 * @param flags: If flags has IB_test set then the file is not actually loaded,
 *                but all other operations take place.
 *
 * @return: A newly allocated ImBuf structure if successful, otherwise NULL.
 */
struct ImBuf *imb_cocoaLoadImage(unsigned char *mem, int size, int flags)
{
	struct ImBuf *ibuf = NULL;
	NSSize bitmapSize;
	uchar *rasterRGB = NULL;
	uchar *rasterRGBA = NULL;
	uchar *toIBuf = NULL;
	int x, y, to_i, from_i;
	NSData *data;
	NSBitmapImageRep *bitmapImage;
	NSBitmapImageRep *blBitmapFormatImageRGB,*blBitmapFormatImageRGBA;
	NSAutoreleasePool *pool;
	
	pool = [[NSAutoreleasePool alloc] init];
	
	data = [NSData dataWithBytes:mem length:size];
	bitmapImage = [[NSBitmapImageRep alloc] initWithData:data];

	if (!bitmapImage) {
		fprintf(stderr, "imb_cocoaLoadImage: error loading image\n");
		[pool drain];
		return NULL;
	}
	
	bitmapSize.width = [bitmapImage pixelsWide];
	bitmapSize.height = [bitmapImage pixelsHigh];
	
	/* Tell cocoa image resolution is same as current system one */
	[bitmapImage setSize:bitmapSize];
	
	/* allocate the image buffer */
	ibuf = IMB_allocImBuf(bitmapSize.width, bitmapSize.height, 32/*RGBA*/, 0);
	if (!ibuf) {
		fprintf(stderr, 
			"imb_cocoaLoadImage: could not allocate memory for the " \
			"image.\n");
		[bitmapImage release];
		[pool drain];
		return NULL;
	}

	/* read in the image data */
	if (!(flags & IB_test)) {

		/* allocate memory for the ibuf->rect */
		imb_addrectImBuf(ibuf);

		/* Convert the image in a RGBA 32bit format */
		/* As Core Graphics does not support contextes with non premutliplied alpha,
		 we need to get alpha key values in a separate batch */
		
		/* First get RGB values w/o Alpha to avoid pre-multiplication, 32bit but last byte is unused */
		blBitmapFormatImageRGB = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
																	  pixelsWide:bitmapSize.width 
																	  pixelsHigh:bitmapSize.height
																   bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:NO
																  colorSpaceName:NSCalibratedRGBColorSpace 
																	bitmapFormat:0
																	 bytesPerRow:4*bitmapSize.width
																	bitsPerPixel:32/*RGB format padded to 32bits*/];
				
		[NSGraphicsContext saveGraphicsState];
		[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGB]];
		[bitmapImage draw];
		[NSGraphicsContext restoreGraphicsState];
		
		rasterRGB = (uchar*)[blBitmapFormatImageRGB bitmapData];
		if (rasterRGB == NULL) {
			[bitmapImage release];
			[blBitmapFormatImageRGB release];
			[pool drain];
			return NULL;
		}

		/* Then get Alpha values by getting the RGBA image (that is premultiplied btw) */
		blBitmapFormatImageRGBA = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
																		  pixelsWide:bitmapSize.width
																		  pixelsHigh:bitmapSize.height
																	   bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
																	  colorSpaceName:NSCalibratedRGBColorSpace
																		bitmapFormat:0
																		 bytesPerRow:4*bitmapSize.width
																		bitsPerPixel:32/* RGBA */];
		
		[NSGraphicsContext saveGraphicsState];
		[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:blBitmapFormatImageRGBA]];
		[bitmapImage draw];
		[NSGraphicsContext restoreGraphicsState];
		
		rasterRGBA = (uchar*)[blBitmapFormatImageRGBA bitmapData];
		if (rasterRGBA == NULL) {
			[bitmapImage release];
			[blBitmapFormatImageRGB release];
			[blBitmapFormatImageRGBA release];
			[pool drain];
			return NULL;
		}
		
		/*Copy the image to ibuf, flipping it vertically*/
		toIBuf = (uchar*)ibuf->rect;
		for (x = 0; x < bitmapSize.width; x++) {
			for (y = 0; y < bitmapSize.height; y++) {
				to_i = (bitmapSize.height-y-1)*bitmapSize.width + x;
				from_i = y*bitmapSize.width + x;

				toIBuf[4*to_i] = rasterRGB[4*from_i]; /* R */
				toIBuf[4*to_i+1] = rasterRGB[4*from_i+1]; /* G */
				toIBuf[4*to_i+2] = rasterRGB[4*from_i+2]; /* B */
				toIBuf[4*to_i+3] = rasterRGBA[4*from_i+3]; /* A */
			}
		}

		[blBitmapFormatImageRGB release];
		[blBitmapFormatImageRGBA release];
	}

	/* release the cocoa objects */
	[bitmapImage release];
	[pool drain];

	if (ENDIAN_ORDER == B_ENDIAN) IMB_convert_rgba_to_abgr(ibuf);

	ibuf->ftype = TIF;
	ibuf->profile = IB_PROFILE_SRGB;

	/* return successfully */
	return (ibuf);
}

/**
 * Saves an image to a file.
 *
 * ImBuf structures with 1, 3 or 4 bytes per pixel (GRAY, RGB, RGBA 
 * respectively) are accepted, and interpreted correctly. 
 *
 * Accepted formats: TIFF, GIF, BMP, PNG, JPEG, JPEG2000
 *
 * @param ibuf:  Image buffer.
 * @param name:  Name of the image file to create.
 * @param flags: Currently largely ignored.
 *
 * @return: 1 if the function is successful, 0 on failure.
 */

#define FTOUSHORT(val) ((val >= 1.0f-0.5f/65535)? 65535: (val <= 0.0f)? 0: (unsigned short)(val*65535.0f + 0.5f))

short imb_cocoaSaveImage(struct ImBuf *ibuf, char *name, int flags)
{
	uint16_t samplesperpixel, bitspersample;
	unsigned char *from = NULL, *to = NULL;
	unsigned short *to16 = NULL;
	float *fromf = NULL;
	int x, y, from_i, to_i, i;
	int success;
	BOOL hasAlpha;
	NSString* colorSpace;
	NSBitmapImageRep *blBitmapFormatImage;
	NSData *dataToWrite;
	NSDictionary *imageProperties;
	
	NSAutoreleasePool *pool;
	
	if (!ibuf) return FALSE;
	if (!name) return FALSE;
	
	/* check for a valid number of bytes per pixel.  Like the PNG writer,
	 * the TIFF writer supports 1, 3 or 4 bytes per pixel, corresponding
	 * to gray, RGB, RGBA respectively. */
	samplesperpixel = (uint16_t)((ibuf->depth + 7) >> 3);
	switch (samplesperpixel) {
		case 4: /*RGBA type*/
			hasAlpha = YES;
			colorSpace = NSCalibratedRGBColorSpace;
			break;
		case 3: /*RGB type*/
			hasAlpha = NO;
			colorSpace = NSCalibratedRGBColorSpace;
			break;
		case 1:
			hasAlpha = NO;
			colorSpace = NSCalibratedWhiteColorSpace;
			break;
		default:
			fprintf(stderr,
					"imb_cocoaSaveImage: unsupported number of bytes per " 
					"pixel: %d\n", samplesperpixel);
			return (0);
	}

	if((ibuf->ftype & TIF_16BIT) && ibuf->rect_float)
		bitspersample = 16;
	else
		bitspersample = 8;

	pool = [[NSAutoreleasePool alloc] init];
	
	/* Create bitmap image rep in blender format */
	blBitmapFormatImage = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
																  pixelsWide:ibuf->x 
																  pixelsHigh:ibuf->y
															   bitsPerSample:bitspersample samplesPerPixel:samplesperpixel hasAlpha:hasAlpha isPlanar:NO
															  colorSpaceName:colorSpace 
																bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
																 bytesPerRow:(ibuf->x*bitspersample*samplesperpixel/8)
																bitsPerPixel:(bitspersample*samplesperpixel)];
	if (!blBitmapFormatImage) {
		[pool drain];
		return FALSE;
	}
	
	/* setup pointers */
	if(bitspersample == 16) {
		fromf = ibuf->rect_float;
		to16   = (unsigned short*)[blBitmapFormatImage bitmapData];
	}
	else {
		from = (unsigned char*)ibuf->rect;
		to   = (unsigned char*)[blBitmapFormatImage bitmapData];
	}

	/* copy pixel data.  While copying, we flip the image vertically. */
	for (x = 0; x < ibuf->x; x++) {
		for (y = 0; y < ibuf->y; y++) {
			from_i = 4*(y*ibuf->x+x);
			to_i   = samplesperpixel*((ibuf->y-y-1)*ibuf->x+x);
			
			if(bitspersample == 16) {
				if (ibuf->profile == IB_PROFILE_SRGB) {
					switch (samplesperpixel) {
						case 4 /*RGBA*/:
							to16[to_i] = FTOUSHORT(linearrgb_to_srgb(fromf[from_i]));
							to16[to_i+1] = FTOUSHORT(linearrgb_to_srgb(fromf[from_i+1]));
							to16[to_i+2] = FTOUSHORT(linearrgb_to_srgb(fromf[from_i+2]));
							to16[to_i+3] = FTOUSHORT(fromf[from_i+3]);
							break;
						case 3 /*RGB*/:
							to16[to_i] = FTOUSHORT(linearrgb_to_srgb(fromf[from_i]));
							to16[to_i+1] = FTOUSHORT(linearrgb_to_srgb(fromf[from_i+1]));
							to16[to_i+2] = FTOUSHORT(linearrgb_to_srgb(fromf[from_i+2]));
							break;
						case 1 /*BW*/:
							to16[to_i] = FTOUSHORT(linearrgb_to_srgb(fromf[from_i]));
							break;
					}
				} 
				else {
					for (i = 0; i < samplesperpixel; i++, to_i++, from_i++)
							to16[to_i] = FTOUSHORT(fromf[from_i]);
				}
			}
			else {
				/* 8bits per sample*/
				for (i = 0; i < samplesperpixel; i++, to_i++, from_i++)
					to[to_i] = from[from_i];
			}
		}
	}
	
	/* generate file data */
	if (IS_tiff(ibuf)) {
		dataToWrite = [blBitmapFormatImage TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:1.0];
	}
	else if (IS_png(ibuf)) {
		imageProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:false], NSImageInterlaced,
						   nil];
		dataToWrite = [blBitmapFormatImage representationUsingType:NSPNGFileType properties:imageProperties];
	}
	else if (IS_bmp(ibuf)) {
		dataToWrite = [blBitmapFormatImage representationUsingType:NSBMPFileType properties:nil];
	}
	else {/* JPEG by default */
		int quality;
		
		quality = ibuf->ftype & 0xff;
		if (quality <= 0) quality = 90; /* Standard quality if wrong supplied*/
		if (quality > 100) quality = 100;
		
		imageProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat:quality], NSImageCompressionFactor,
						   [NSNumber numberWithBool:true], NSImageProgressive, 
						   nil];
		dataToWrite = [blBitmapFormatImage representationUsingType:NSJPEGFileType properties:imageProperties];
	}

	/* Write the file */
	success = [dataToWrite writeToFile:[NSString stringWithCString:name encoding:NSISOLatin1StringEncoding]
				  atomically:YES];

	[blBitmapFormatImage release];
	[pool drain];
	
	return success;
}

#pragma mark format checking functions

/* Currently, only tiff format is handled, so need to include here function that was previously in tiff.c */

/**
 * Checks whether a given memory buffer contains a TIFF file.
 *
 * FIXME: Possible memory leak if mem is less than IMB_TIFF_NCB bytes long.
 *        However, changing this will require up-stream modifications.
 *
 * This method uses the format identifiers from:
 *     http://www.faqs.org/faqs/graphics/fileformats-faq/part4/section-9.html
 * The first four bytes of big-endian and little-endian TIFF files
 * respectively are (hex):
 * 	4d 4d 00 2a
 * 	49 49 2a 00
 * Note that TIFF files on *any* platform can be either big- or little-endian;
 * it's not platform-specific.
 *
 * AFAICT, libtiff doesn't provide a method to do this automatically, and
 * hence my manual comparison. - Jonathan Merritt (lancelet) 4th Sept 2005.
 */
#define IMB_TIFF_NCB 4		/* number of comparison bytes used */
int imb_is_a_tiff(void *mem)
{
	char big_endian[IMB_TIFF_NCB] = { 0x4d, 0x4d, 0x00, 0x2a };
	char lil_endian[IMB_TIFF_NCB] = { 0x49, 0x49, 0x2a, 0x00 };
	
	return ( (memcmp(big_endian, mem, IMB_TIFF_NCB) == 0) ||
			(memcmp(lil_endian, mem, IMB_TIFF_NCB) == 0) );
}
