JPEGReader.st
changeset 2971 07bd4e98036e
parent 2768 38e9b42f2af8
child 2972 c9b3ef795181
--- a/JPEGReader.st	Tue Nov 29 19:12:36 2011 +0100
+++ b/JPEGReader.st	Mon Dec 12 21:19:08 2011 +0100
@@ -13,7 +13,7 @@
 
 ImageReader subclass:#JPEGReader
 	instanceVariableNames:'jpeg_decompress_struct jpeg_error_mgr_struct colorComponents
-		forceGrayscale forceDitherMode'
+		forceGrayscale forceDitherMode app1SegmentHandler'
 	classVariableNames:'ErrorPrinting'
 	poolDictionaries:''
 	category:'Graphics-Images-Readers'
@@ -131,7 +131,23 @@
   }
 }
 
-#endif
+#endif /* PROGRESS_REPORT */
+
+
+/* fetch a byte from the stream */
+unsigned int
+JPG_jpeg_getc (j_decompress_ptr cinfo)
+/* Read next byte */
+{
+  struct jpeg_source_mgr *datasrc = cinfo->src;
+
+  if (datasrc->bytes_in_buffer == 0) {
+    if (! (*datasrc->fill_input_buffer) (cinfo))
+      return -1;
+  }
+  datasrc->bytes_in_buffer--;
+  return (*datasrc->next_input_byte++) & 0xFF;
+}
 
 %}
 ! !
@@ -200,6 +216,12 @@
 
 !JPEGReader methodsFor:'accessing'!
 
+app1SegmentHandler:aBlock
+    "set a handler block for app1 segment data (geolocation in exif format)"
+
+    app1SegmentHandler := aBlock
+!
+
 forceDitherMode:something
     "set the dither mode, to one of #none or #ordered"
 
@@ -220,8 +242,56 @@
 
 !JPEGReader methodsFor:'private'!
 
+app1SegmentCallback
+    "return a callback function which invokes the app1SegmentHandlerBlock if defined.
+     This will be called to handle the exif segment, containing geolocation tags.
+     Return nil, if there is no handler blcok defined"
+
+    |cb|
+
+    app1SegmentHandler isNil ifTrue:[^ nil].
+
+    cb := ExternalFunctionCallback new.
+    cb returnType:#bool argumentTypes:#(pointer).
+    cb generateClosure.
+    cb action:[:args | self fetchApp1SegmentData. true].
+    ^ cb code.  'can be passed to C'.
+!
+
+fetchApp1SegmentData
+    |byte1 byte2 count|
+
+    byte1 := self jpeg_getc.
+    byte2 := self jpeg_getc.
+    count := (byte1 bitShift:8) + byte2. "/ msb first
+    count := count - 2. "/ count itself is included
+    data := ByteArray new:count.
+    1 to: count do:[:i |
+	data at:i put:(self jpeg_getc).
+    ].
+    self halt.
+    app1SegmentHandler value:data.
+!
+
+jpeg_getc
+%{
+    OBJ j_d_s = __INST(jpeg_decompress_struct);
+
+    if (__isExternalBytesLike(j_d_s)) {
+	struct jpeg_decompress_struct *cinfoPtr;
+	int byte;
+
+	cinfoPtr = (struct jpeg_decompress_struct *)(__externalBytesAddress(j_d_s));
+
+	byte = JPG_jpeg_getc (cinfoPtr);
+	RETURN( __MKSMALLINT(byte) );
+    }
+%}.
+    self primitiveFailed
+!
+
 create_jpeg_decompress_struct
-    |errMgrStructSize decompressStructSize fp errorOccurred|
+    |errMgrStructSize decompressStructSize fp errorOccurred app1SegmentCallbackFunction|
 
     fp := inStream filePointer.
     fp isNil ifTrue:[
@@ -229,6 +299,8 @@
 	^ self.
     ].
 
+    app1SegmentCallbackFunction := self app1SegmentCallback.
+
 %{
     errMgrStructSize = __mkSmallInteger(sizeof(struct my_error_mgr));
     decompressStructSize = __mkSmallInteger(sizeof(struct jpeg_decompress_struct));
@@ -279,6 +351,19 @@
 	 */
 	jpeg_set_marker_processor(cinfoPtr, JPEG_COM, COM_handler);
 #endif
+	if (app1SegmentCallbackFunction != nil) {
+	    jpeg_marker_parser_method cb = NULL;
+
+	    if (__isExternalFunction(app1SegmentCallbackFunction)) {
+		cb = (jpeg_marker_parser_method)__externalFunctionVal(app1SegmentCallbackFunction);
+	    } else if (__isExternalAddress(app1SegmentCallbackFunction)) {
+		cb = (jpeg_marker_parser_method)__externalAddressVal(app1SegmentCallbackFunction);
+	    } else {
+		/* ignore, but should report an error... */
+	    }
+	    jpeg_set_marker_processor(cinfoPtr, JPEG_APP0+1, cb);
+	}
+
 	cinfoPtr->err->trace_level = 0;
 
 #if 0
@@ -479,22 +564,22 @@
     |dataIdx bytesPerRow returnCode pos1 ok tmpFile s|
 
     aStream isExternalStream ifFalse:[
-        "/ libJpeg can only handle real OS-streams
+	"/ libJpeg can only handle real OS-streams
 
-        tmpFile := Filename newTemporary.
-        [
-            s := tmpFile writeStream binary.
-            s nextPutAll:aStream contents.
-            s close.
-            s := tmpFile readStream binary.
-            ^ self fromStream:s.
-        ] ensure:[
-            s notNil ifTrue:[s close].
-            tmpFile delete.
-        ].
+	tmpFile := Filename newTemporary.
+	[
+	    s := tmpFile writeStream binary.
+	    s nextPutAll:aStream contents.
+	    s close.
+	    s := tmpFile readStream binary.
+	    ^ self fromStream:s.
+	] ensure:[
+	    s notNil ifTrue:[s close].
+	    tmpFile delete.
+	].
 
-        "/ 'JPEGReader [info]: can only read from real streams' infoPrintCR.
-        "/ ^ nil
+	"/ 'JPEGReader [info]: can only read from real streams' infoPrintCR.
+	"/ ^ nil
     ].
 
     inStream := aStream.
@@ -503,27 +588,27 @@
 
     (self create_jpeg_decompress_struct not
     or:[self start_decompress not]) ifTrue:[
-        ok := false.
+	ok := false.
 
-        "/ if there was no SOI marker,
-        "/ try again, skipping first 128 bytes
-        "/ (seems to be generated by some jpg writers)
+	"/ if there was no SOI marker,
+	"/ try again, skipping first 128 bytes
+	"/ (seems to be generated by some jpg writers)
 
-        inStream position:pos1.
-        ((inStream nextByte ~~ 16rFF)
-        or:[inStream nextByte ~~ 16rD8]) ifTrue:[
-            inStream position:pos1 + 128.
-            ((inStream nextByte == 16rFF)
-            and:[inStream nextByte == 16rD8]) ifTrue:[
-                inStream position:pos1 + 128.
-                ok := self create_jpeg_decompress_struct
-                      and:[self start_decompress]
-            ].
-        ].
-        ok ifFalse:[
-            'JPEGReader [info]: ' infoPrint. self get_error_message infoPrintCR.
-            ^ nil
-        ]
+	inStream position:pos1.
+	((inStream nextByte ~~ 16rFF)
+	or:[inStream nextByte ~~ 16rD8]) ifTrue:[
+	    inStream position:pos1 + 128.
+	    ((inStream nextByte == 16rFF)
+	    and:[inStream nextByte == 16rD8]) ifTrue:[
+		inStream position:pos1 + 128.
+		ok := self create_jpeg_decompress_struct
+		      and:[self start_decompress]
+	    ].
+	].
+	ok ifFalse:[
+	    'JPEGReader [info]: ' infoPrint. self get_error_message infoPrintCR.
+	    ^ nil
+	]
     ].
 
     data := ByteArray uninitializedNew:(width * height * colorComponents).
@@ -531,27 +616,27 @@
     bytesPerRow := colorComponents * width.
 
     [(returnCode := self decompressChunkInto:data startingAt:dataIdx) > 0] whileTrue:[
-        "/ got a row in the buffer ...
-        dataIdx := dataIdx + bytesPerRow
+	"/ got a row in the buffer ...
+	dataIdx := dataIdx + bytesPerRow
     ].
     returnCode < 0 ifTrue:[
-        'JPEGReader [info]: ' infoPrint. self get_error_message infoPrintCR.
-        ^ nil
+	'JPEGReader [info]: ' infoPrint. self get_error_message infoPrintCR.
+	^ nil
     ].
 
     (self finish_decompress) ifFalse:[
-        'JPEGReader [info]: ' infoPrint. self get_error_message infoPrintCR.
-        ^ nil
+	'JPEGReader [info]: ' infoPrint. self get_error_message infoPrintCR.
+	^ nil
     ].
 
     colorComponents == 3 ifTrue:[
-        photometric := #rgb.
-        samplesPerPixel := 3.
-        bitsPerSample := #(8 8 8).
+	photometric := #rgb.
+	samplesPerPixel := 3.
+	bitsPerSample := #(8 8 8).
     ] ifFalse:[
-        photometric := #blackIs0.
-        samplesPerPixel := 1.
-        bitsPerSample := #(8).
+	photometric := #blackIs0.
+	samplesPerPixel := 1.
+	bitsPerSample := #(8).
     ].
 
     "
@@ -574,11 +659,11 @@
 !JPEGReader class methodsFor:'documentation'!
 
 version
-    ^ '$Header: /cvs/stx/stx/libview2/JPEGReader.st,v 1.50 2009-10-21 16:52:38 stefan Exp $'
+    ^ '$Header: /cvs/stx/stx/libview2/JPEGReader.st,v 1.51 2011-12-12 20:19:08 cg Exp $'
 !
 
 version_CVS
-    ^ '$Header: /cvs/stx/stx/libview2/JPEGReader.st,v 1.50 2009-10-21 16:52:38 stefan Exp $'
+    ^ '$Header: /cvs/stx/stx/libview2/JPEGReader.st,v 1.51 2011-12-12 20:19:08 cg Exp $'
 ! !
 
 JPEGReader initialize!