OpenCV 2.4.11でJPEG画像を自動回転する

OpenCVでJPEGの自動回転できるのか、という宿題がでたので、回答を。あ、大部分はlibjpegのマクロ使っていますのでので。

使い方は、
`imread(filename , 128 + 1);`とか`imread(filename , 128 + 3);`ですね。

flagsに-1を指定する事もできるはずですが、まあJPEG画像だから気にしない（PNG画像ならば透過チャネルも読めるはず）。

あとは90度単位の回転がもっと速いロジックになっていればなあ……と嘆くばかりです。

```c:patch.patch
anobiidae@anobiidae-virtual-machine:~/opencv2$ diff opencv-2.4.11/modules/highgui/src opencv-2.4.11.auto/modules/highgui/src/ -c
共通のサブディレクトリー: opencv-2.4.11/modules/highgui/src/files_Qt と opencv-2.4.11.auto/modules/highgui/src/files_Qt
diff -c opencv-2.4.11/modules/highgui/src/grfmt_jpeg.cpp opencv-2.4.11.auto/modules/highgui/src/grfmt_jpeg.cpp
*** opencv-2.4.11/modules/highgui/src/grfmt_jpeg.cpp	2015-02-25 21:10:31.000000000 +0900
--- opencv-2.4.11.auto/modules/highgui/src/grfmt_jpeg.cpp	2015-08-13 08:32:11.492022640 +0900
***************
*** 102,107 ****
--- 102,108 ----
      jpeg_decompress_struct cinfo; // IJG JPEG codec structure
      JpegErrorMgr jerr; // error processing manager state
      JpegSource source; // memory buffer source
+     int m_orientation; // Orientation
  };
  
  /////////////////////// Error processing /////////////////////
***************
*** 167,172 ****
--- 168,342 ----
      longjmp( err_mgr->setjmp_buffer, 1 );
  }
  
+ /*
+  * Parse the DHT table.
+  * This macro comes from jpeg9a (jdmarker.c).
+  */
+ 
+ #define MAKESTMT(stuff)		do { stuff } while (0)
+ /*
+  * Macros for fetching data from the data source module.
+  *
+  * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect
+  * the current restart point; we update them only when we have reached a
+  * suitable place to restart if a suspension occurs.
+  */
+ 
+ /* Declare and initialize local copies of input pointer/count */
+ #define INPUT_VARS(cinfo)  \
+ 	struct jpeg_source_mgr * datasrc = (cinfo)->src;  \
+ 	const JOCTET * next_input_byte = datasrc->next_input_byte;  \
+ 	size_t bytes_in_buffer = datasrc->bytes_in_buffer
+ 
+ /* Unload the local copies --- do this only at a restart boundary */
+ #define INPUT_SYNC(cinfo)  \
+ 	( datasrc->next_input_byte = next_input_byte,  \
+ 	  datasrc->bytes_in_buffer = bytes_in_buffer )
+ 
+ /* Reload the local copies --- used only in MAKE_BYTE_AVAIL */
+ #define INPUT_RELOAD(cinfo)  \
+ 	( next_input_byte = datasrc->next_input_byte,  \
+ 	  bytes_in_buffer = datasrc->bytes_in_buffer )
+ 
+ /* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available.
+  * Note we do *not* do INPUT_SYNC before calling fill_input_buffer,
+  * but we must reload the local copies after a successful fill.
+  */
+ #define MAKE_BYTE_AVAIL(cinfo,action)  \
+ 	if (bytes_in_buffer == 0) {  \
+ 	  if (! (*datasrc->fill_input_buffer) (cinfo))  \
+ 	    { action; }  \
+ 	  INPUT_RELOAD(cinfo);  \
+ 	}
+ 
+ /* Read a byte into variable V.
+  * If must suspend, take the specified action (typically "return FALSE").
+  */
+ #define INPUT_BYTE(cinfo,V,action)  \
+ 	MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \
+ 		  bytes_in_buffer--; \
+ 		  V = GETJOCTET(*next_input_byte++); )
+ 
+ /* As above, but read two bytes interpreted as an unsigned 16-bit integer.
+  * V should be declared unsigned int or perhaps INT32.
+  */
+ #define INPUT_2BYTES(cinfo,V,action)  \
+ 	MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \
+ 		  bytes_in_buffer--; \
+ 		  V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \
+ 		  MAKE_BYTE_AVAIL(cinfo,action); \
+ 		  bytes_in_buffer--; \
+ 		  V += GETJOCTET(*next_input_byte++); )
+ 
+ /* As above, but read four bytes interpreted as an unsigned 32-bit integer.
+  * V should be declared unsigned int.
+  */
+ #define INPUT_4BYTES(cinfo,V,action)  \
+ 	MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \
+ 		  bytes_in_buffer--; \
+ 		  V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 24; \
+ 		  MAKE_BYTE_AVAIL(cinfo,action); \
+ 		  bytes_in_buffer--; \
+ 		  V += (GETJOCTET(*next_input_byte++)) << 16; \
+ 		  MAKE_BYTE_AVAIL(cinfo,action); \
+ 		  bytes_in_buffer--; \
+ 		  V += (GETJOCTET(*next_input_byte++)) << 8; \
+ 		  MAKE_BYTE_AVAIL(cinfo,action); \
+ 		  bytes_in_buffer--; \
+ 		  V += (GETJOCTET(*next_input_byte++)); )
+ /*
+  * Routines for processing APPn and COM markers.
+  * These are either saved in memory or discarded, per application request.
+  * APP0 and APP14 are specially checked to see if they are
+  * JFIF and Adobe markers, respectively.
+  */
+ 
+ #define APP1_DATA_LEN	10	/* Length of interesting data in APP14 */
+ 
+ #define SWAP_2BYTES(V) \
+ 	{ V = (!isLE)?V:( ((V >> 8) & 0xff) | (V << 8 ) ); }
+ #define SWAP_4BYTES(V) \
+ 	{ V = (!isLE)?V:( ((V >> 24) & 0xff) | ((V >> 8) & 0xff00) | ((V << 8) & 0xff0000) | (V << 24 ) ); }
+ 
+ METHODDEF(int)
+ proc_exif(j_decompress_ptr cinfo)
+ {
+   bool isLE = false;
+   INT32 length;
+   JOCTET b[APP1_DATA_LEN];
+   unsigned int i, numtoread;
+   INPUT_VARS(cinfo);
+ 
+   INPUT_2BYTES(cinfo, length, return FALSE);
+   length -= 2;
+ 
+   /* get the interesting part of the marker data */
+   if (length >= APP1_DATA_LEN)
+     numtoread = APP1_DATA_LEN;
+   else if (length > 0)
+     numtoread = (unsigned int) length;
+   else
+     numtoread = 0;
+   for (i = 0; i < numtoread; i++)
+     INPUT_BYTE(cinfo, b[i], return FALSE);
+   length -= numtoread;
+ 
+   if ( memcmp(b,"Exif\0\0",6) != 0 ) {
+     printf("Exif tag is not alive\n");
+     goto exit;
+   }
+ 
+   if ( memcmp(b+6,"MM",2) == 0 ) {
+     isLE = false;
+   }else{
+     printf("Endian is not MM\n");
+     goto exit;
+   }
+ 
+  	unsigned int ifd_offset;
+  INPUT_4BYTES(cinfo, ifd_offset, return FALSE);
+  SWAP_4BYTES(ifd_offset);
+  length -= 4;
+  if(ifd_offset != 8 ) {
+    printf("IFD should be next to header");
+    goto exit;
+  }
+ 
+  {
+    int tag_num;
+    INPUT_2BYTES(cinfo, tag_num, return FALSE ); SWAP_2BYTES(tag_num);
+    length-=2;
+    for(; tag_num > 0 ; tag_num -- )
+    {
+      int tag_tag, tag_type, tag_count;
+      int tag_offset, tag_dmy; 
+ 
+      INPUT_2BYTES(cinfo, tag_tag,    return FALSE ); SWAP_2BYTES(tag_tag);
+      INPUT_2BYTES(cinfo, tag_type,   return FALSE ); SWAP_2BYTES(tag_type);
+      INPUT_4BYTES(cinfo, tag_count,  return FALSE ); SWAP_4BYTES(tag_count);
+      if(tag_type == 0x03 /* short */)
+      {
+        INPUT_2BYTES(cinfo, tag_offset, return FALSE ); SWAP_2BYTES(tag_offset);
+        INPUT_2BYTES(cinfo, tag_dmy,    return FALSE );
+      }else{
+        INPUT_4BYTES(cinfo, tag_offset, return FALSE ); SWAP_4BYTES(tag_offset);
+      }
+      if(tag_tag == 274 /* Orientation */ ){
+         JpegState* state = (JpegState*)cinfo; // HACKME!
+         state->m_orientation = tag_offset;
+      }
+      length -= 12;
+    }
+  }
+ 
+ exit:
+   /* skip any remaining data -- could be lots */
+   INPUT_SYNC(cinfo);
+   if (length > 0)
+     (*cinfo->src->skip_input_data) (cinfo, (long) length);
+ 
+   return TRUE;
+ }
  
  /////////////////////// JpegDecoder ///////////////////
  
***************
*** 220,225 ****
--- 390,396 ----
      m_state = state;
      state->cinfo.err = jpeg_std_error(&state->jerr.pub);
      state->jerr.pub.error_exit = error_exit;
+     state->m_orientation = 1; // default;
  
      if( setjmp( state->jerr.setjmp_buffer ) == 0 )
      {
***************
*** 240,250 ****
--- 411,423 ----
  
          if (state->cinfo.src != 0)
          {
+ 				    jpeg_set_marker_processor( &state->cinfo, JPEG_APP0+1, proc_exif );
              jpeg_read_header( &state->cinfo, TRUE );
  
              m_width = state->cinfo.image_width;
              m_height = state->cinfo.image_height;
              m_type = state->cinfo.num_components > 1 ? CV_8UC3 : CV_8UC1;
+             m_orientation = state->m_orientation;
              result = true;
          }
      }
***************
*** 450,455 ****
--- 623,630 ----
                                                JPOOL_IMAGE, m_width*4, 1 );
  
              uchar* data = img.data;
+             if(*data == 0){ m_orientation = 1; }
+ 
              for( ; m_height--; data += step )
              {
                  jpeg_read_scanlines( cinfo, buffer, 1 );
***************
*** 470,475 ****
--- 645,678 ----
              }
              result = true;
              jpeg_finish_decompress( cinfo );
+ 
+             {
+               struct {
+                 bool isFlip;
+                 int  rotateAngle;
+               } tLUT[] = {
+                 {false,0},
+                 {false,  0},{true ,  0},{false,180},{true ,180},
+                 {true , 90},{false, 90},{true ,270},{false ,270},
+               };
+ 
+               printf("FLAG=%d\n", m_orientation );
+ 
+               if(tLUT[m_orientation].rotateAngle == 90){
+                 transpose(img,img);
+                 flip(img,img, 1);
+               }
+               if(tLUT[m_orientation].rotateAngle == 180){
+                 flip(img,img,-1);
+               }
+               if(tLUT[m_orientation].rotateAngle == 270){
+                 transpose(img, img);
+                 flip(img,img,0);
+               }
+               if(tLUT[m_orientation].isFlip == true ){
+                 flip(img,img, 1);
+               }
+             }
          }
      }
  
diff -c opencv-2.4.11/modules/highgui/src/grfmt_jpeg.hpp opencv-2.4.11.auto/modules/highgui/src/grfmt_jpeg.hpp
*** opencv-2.4.11/modules/highgui/src/grfmt_jpeg.hpp	2015-02-25 21:10:31.000000000 +0900
--- opencv-2.4.11.auto/modules/highgui/src/grfmt_jpeg.hpp	2015-08-13 07:23:17.984141888 +0900
***************
*** 67,75 ****
      ImageDecoder newDecoder() const;
  
  protected:
- 
      FILE* m_f;
      void* m_state;
  };
  
  
--- 67,75 ----
      ImageDecoder newDecoder() const;
  
  protected:
      FILE* m_f;
      void* m_state;
+     int   m_orientation;
  };
  
  
diff -c opencv-2.4.11/modules/highgui/src/loadsave.cpp opencv-2.4.11.auto/modules/highgui/src/loadsave.cpp
*** opencv-2.4.11/modules/highgui/src/loadsave.cpp	2015-02-25 21:10:31.000000000 +0900
--- opencv-2.4.11.auto/modules/highgui/src/loadsave.cpp	2015-08-13 08:29:48.204026773 +0900
***************
*** 242,247 ****
--- 242,250 ----
          temp = cvarrToMat(image);
      }
  
+     // Auto-rotation flag is stored at first bytes
+     data->at<unsigned char>(0,0) = flags >> 7; 
+ 
      if( !decoder->readData( *data ))
      {
          cvReleaseImage( &image );
```