/*****************************************************************************/
// Copyright 2011 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_jpeg_image.cpp#1 $ */ 
/* $DateTime: 2012/05/30 13:28:51 $ */
/* $Change: 832332 $ */
/* $Author: tknoll $ */

/*****************************************************************************/

#include "dng_jpeg_image.h"

#include "dng_abort_sniffer.h"
#include "dng_area_task.h"
#include "dng_assertions.h"
#include "dng_host.h"
#include "dng_ifd.h"
#include "dng_image.h"
#include "dng_image_writer.h"
#include "dng_memory_stream.h"
#include "dng_mutex.h"
#include "dng_safe_arithmetic.h"

/*****************************************************************************/

dng_jpeg_image::dng_jpeg_image ()

	:	fImageSize  ()
	,	fTileSize   ()
	,	fUsesStrips (false)
	,	fJPEGTables ()
	,	fJPEGData   ()
	
	{
	
	}

/*****************************************************************************/

class dng_jpeg_image_encode_task : public dng_area_task
	{
	
	private:
	
		dng_host &fHost;
		
		dng_image_writer &fWriter;
		
		const dng_image &fImage;
	
		dng_jpeg_image &fJPEGImage;
		
		uint32 fTileCount;
		
		const dng_ifd &fIFD;
				
		dng_mutex fMutex;
		
		uint32 fNextTileIndex;
		
	public:
	
		dng_jpeg_image_encode_task (dng_host &host,
									dng_image_writer &writer,
									const dng_image &image,
									dng_jpeg_image &jpegImage,
									uint32 tileCount,
									const dng_ifd &ifd)
		
			:	fHost			  (host)
			,	fWriter			  (writer)
			,	fImage			  (image)
			,	fJPEGImage        (jpegImage)
			,	fTileCount		  (tileCount)
			,	fIFD		      (ifd)
			,	fMutex			  ("dng_jpeg_image_encode_task")
			,	fNextTileIndex	  (0)
			
			{
			
			fMinTaskArea = 16 * 16;
			fUnitCell    = dng_point (16, 16);
			fMaxTileSize = dng_point (16, 16);
			
			}
	
		void Process (uint32 /* threadIndex */,
					  const dng_rect & /* tile */,
					  dng_abort_sniffer *sniffer)
			{
			
			AutoPtr<dng_memory_block> compressedBuffer;
			AutoPtr<dng_memory_block> uncompressedBuffer;
			AutoPtr<dng_memory_block> subTileBlockBuffer;
			AutoPtr<dng_memory_block> tempBuffer;
			
			uint32 uncompressedSize = SafeUint32Mult (
				fIFD.fTileLength, fIFD.fTileWidth, fIFD.fSamplesPerPixel);
			
			uncompressedBuffer.Reset (fHost.Allocate (uncompressedSize));
			
			uint32 tilesAcross = fIFD.TilesAcross ();
	
			while (true)
				{
				
				uint32 tileIndex;
				
					{
					
					dng_lock_mutex lock (&fMutex);
					
					if (fNextTileIndex == fTileCount)
						{
						return;
						}
						
					tileIndex = fNextTileIndex++;
										
					}
					
				dng_abort_sniffer::SniffForAbort (sniffer);
				
				uint32 rowIndex = tileIndex / tilesAcross;
				uint32 colIndex = tileIndex % tilesAcross;
				
				dng_rect tileArea = fIFD.TileArea (rowIndex, colIndex);
				
				dng_memory_stream stream (fHost.Allocator ());
				
				fWriter.WriteTile (fHost,
								   fIFD,
								   stream,
								   fImage,
								   tileArea,
								   1,
								   compressedBuffer,
								   uncompressedBuffer,
								   subTileBlockBuffer,
								   tempBuffer);
								  
				fJPEGImage.fJPEGData [tileIndex].Reset (stream.AsMemoryBlock (fHost.Allocator ()));
					
				}
			
			}
		
	private:

		// Hidden copy constructor and assignment operator.

		dng_jpeg_image_encode_task (const dng_jpeg_image_encode_task &);

		dng_jpeg_image_encode_task & operator= (const dng_jpeg_image_encode_task &);
		
	};

/*****************************************************************************/

void dng_jpeg_image::Encode (dng_host &host,
							 const dng_negative &negative,
							 dng_image_writer &writer,
							 const dng_image &image)
	{
	
	#if qDNGValidate
	dng_timer timer ("Encode JPEG Proxy time");
	#endif
	
	DNG_ASSERT (image.PixelType () == ttByte, "Cannot JPEG encode non-byte image");
	
	fImageSize = image.Bounds ().Size ();
	
	dng_ifd ifd;
	
	ifd.fImageWidth  = fImageSize.h;
	ifd.fImageLength = fImageSize.v;
	
	ifd.fSamplesPerPixel = image.Planes ();
	
	ifd.fBitsPerSample [0] = 8;
	ifd.fBitsPerSample [1] = 8;
	ifd.fBitsPerSample [2] = 8;
	ifd.fBitsPerSample [3] = 8;
	
	ifd.fPhotometricInterpretation = piLinearRaw;
	
	ifd.fCompression = ccLossyJPEG;
	
	ifd.FindTileSize (512 * 512 * ifd.fSamplesPerPixel);
	
	fTileSize.h = ifd.fTileWidth;
	fTileSize.v = ifd.fTileLength;
	
	// Need a higher quality for raw proxies than non-raw proxies,
	// since users often perform much greater color changes.  Also, use
	// we are targeting a "large" size proxy (larger than 5MP pixels), or this
	// is a full size proxy, then use a higher quality.
	
	bool useHigherQuality = (uint64) ifd.fImageWidth *
							(uint64) ifd.fImageLength > 5000000 ||
							image.Bounds ().Size () == negative.OriginalDefaultFinalSize ();
	
	if (negative.ColorimetricReference () == crSceneReferred)
		{
		ifd.fCompressionQuality = useHigherQuality ? 11 : 10;
		}
	else
		{
		ifd.fCompressionQuality = useHigherQuality ? 10 : 8;
		}
	
	uint32 tilesAcross = ifd.TilesAcross ();
	uint32 tilesDown   = ifd.TilesDown   ();
	
	uint32 tileCount = tilesAcross * tilesDown;
	
	fJPEGData.Reset (tileCount);
	
	uint32 threadCount = Min_uint32 (tileCount,
									 host.PerformAreaTaskThreads ());
										 
	dng_jpeg_image_encode_task task (host,
									 writer,
									 image,
									 *this,
									 tileCount,
									 ifd);
									  
	host.PerformAreaTask (task,
						  dng_rect (0, 0, 16, 16 * threadCount));
		
	}
			
/*****************************************************************************/

class dng_jpeg_image_find_digest_task : public dng_area_task
	{
	
	private:
	
		const dng_jpeg_image &fJPEGImage;
		
		uint32 fTileCount;
		
		dng_fingerprint *fDigests;
				
		dng_mutex fMutex;
		
		uint32 fNextTileIndex;
		
	public:
	
		dng_jpeg_image_find_digest_task (const dng_jpeg_image &jpegImage,
										 uint32 tileCount,
										 dng_fingerprint *digests)
		
			:	fJPEGImage        (jpegImage)
			,	fTileCount		  (tileCount)
			,	fDigests		  (digests)
			,	fMutex			  ("dng_jpeg_image_find_digest_task")
			,	fNextTileIndex	  (0)
			
			{
			
			fMinTaskArea = 16 * 16;
			fUnitCell    = dng_point (16, 16);
			fMaxTileSize = dng_point (16, 16);
			
			}
	
		void Process (uint32 /* threadIndex */,
					  const dng_rect & /* tile */,
					  dng_abort_sniffer *sniffer)
			{
			
			while (true)
				{
				
				uint32 tileIndex;
				
					{
					
					dng_lock_mutex lock (&fMutex);
					
					if (fNextTileIndex == fTileCount)
						{
						return;
						}
						
					tileIndex = fNextTileIndex++;
										
					}
					
				dng_abort_sniffer::SniffForAbort (sniffer);
				
				dng_md5_printer printer;
				
				printer.Process (fJPEGImage.fJPEGData [tileIndex]->Buffer      (),
								 fJPEGImage.fJPEGData [tileIndex]->LogicalSize ());
								 
				fDigests [tileIndex] = printer.Result ();
					
				}
			
			}
		
	private:

		// Hidden copy constructor and assignment operator.

		dng_jpeg_image_find_digest_task (const dng_jpeg_image_find_digest_task &);

		dng_jpeg_image_find_digest_task & operator= (const dng_jpeg_image_find_digest_task &);
		
	};

/*****************************************************************************/

dng_fingerprint dng_jpeg_image::FindDigest (dng_host &host) const
	{
	
	uint32 tileCount = TileCount ();
	
	uint32 arrayCount = tileCount + (fJPEGTables.Get () ? 1 : 0);
	
	AutoArray<dng_fingerprint> digests (arrayCount);
	
	// Compute digest of each compressed tile.

		{
		
		uint32 threadCount = Min_uint32 (tileCount,
										 host.PerformAreaTaskThreads ());
										 
		dng_jpeg_image_find_digest_task task (*this,
											  tileCount,
											  digests.Get ());
										  
		host.PerformAreaTask (task,
							  dng_rect (0, 0, 16, 16 * threadCount));
		
		}
	
	// Compute digest of JPEG tables, if any.
		
	if (fJPEGTables.Get ())
		{
		
		dng_md5_printer printer;
		
		printer.Process (fJPEGTables->Buffer      (),
						 fJPEGTables->LogicalSize ());
						 
		digests [tileCount] = printer.Result ();
		
		}
		
	// Combine digests into a single digest.
	
		{
		
		dng_md5_printer printer;
		
		for (uint32 k = 0; k < arrayCount; k++)
			{
		
			printer.Process (digests [k].data,
							 dng_fingerprint::kDNGFingerprintSize);
							 
			}
			
		return printer.Result ();
		
		}
	
	}
			
/*****************************************************************************/