Skip to content

Commit

Permalink
Add support for auto-rotating images according to the EXIF orientatio…
Browse files Browse the repository at this point in the history
…n tag

Not implemented for multi-page images yet (see technical details after * below).

Example images can be found at https://github.com/recurser/exif-orientation-examples.git

If the image has an EXIF orientation tag, its abbreviated representation (“code”) is appended to the viewer title, e.g. “R0F0” (no rotation, no flip) or “R1FX” (rotate 90 degrees and flip horizontally). If the script did not auto-rotate the image according to the tag (e.g. because it is a multi-page TIFF image), the code is in lowercase, e.g. “r1f0” (you need to tilt your head to the left to view the image).

* Technicalities about multi-page images:
To implement the same for multi-page images, one will need to call GdipCloneImage prior to GdipImageRotateFlip (as the latter discards all other pages of the image), apply the rotation/flip to the clone, display the clone, then dispose it appropriately BUT still call GdipImageSelectActiveFrame for the original image.
  • Loading branch information
HamRusTal committed May 23, 2020
1 parent e3b7f68 commit 600bb53
Showing 1 changed file with 63 additions and 10 deletions.
73 changes: 63 additions & 10 deletions LuaPicture/Macros/scripts/picture.lua
@@ -1,4 +1,4 @@
-- See http://forum.farmanager.com/viewtopic.php?p=112243#p112243
-- See http://forum.farmanager.com/viewtopic.php?p=112243#p112243
local function Set(list)
local set={}
for _,l in ipairs(list) do set[l]=true end
Expand Down Expand Up @@ -78,6 +78,26 @@ typedef struct PropertyItem {
void* value;
} PropertyItem;
]])
safe_cdef([[
typedef enum RotateFlipType {
RotateNoneFlipNone,
Rotate90FlipNone,
Rotate180FlipNone,
Rotate270FlipNone,
RotateNoneFlipX,
Rotate90FlipX,
Rotate180FlipX,
Rotate270FlipX,
RotateNoneFlipY,
Rotate90FlipY,
Rotate180FlipY,
Rotate270FlipY,
RotateNoneFlipXY,
Rotate90FlipXY,
Rotate180FlipXY,
Rotate270FlipXY
} RotateFlipType;
]])
ffi.cdef[[
GpStatus GdiplusStartup(void**,const GdiplusStartupInput*,GdiplusStartupOutput*);
void GdiplusShutdown(void*);
Expand All @@ -99,6 +119,7 @@ GpStatus GdipCreateBitmapFromScan0(int,int,int,int,unsigned char*,void** GpBitma
GpStatus GdipGetImageGraphicsContext(void* GpImage,void** GpGraphics);
GpStatus GdipGetPropertyItemSize(void* GpImage,unsigned long,unsigned int*);
GpStatus GdipGetPropertyItem(void* GpImage,unsigned long,unsigned int,PropertyItem*);
GpStatus GdipImageRotateFlip(void* GpImage,RotateFlipType);
]]
safe_cdef([[
typedef struct _COORD {
Expand Down Expand Up @@ -443,6 +464,20 @@ local function InitBGColor()
BGColor=function() return bgcolor end
end

local RotFlipByOrient={ -- From exiftool documentation
[1]={tr=false,op='RotateNoneFlipNone',mn='R0F0'},-- Horizontal (normal)
[2]={tr=false,op='RotateNoneFlipX' ,mn='R0FX'},-- Mirror horizontal
[3]={tr=false,op='Rotate180FlipNone' ,mn='R2F0'},-- Rotate 180
[4]={tr=false,op='Rotate180FlipX' ,mn='R2FX'},-- Mirror vertical
[5]={tr=true ,op='Rotate90FlipX' ,mn='R1FX'},-- Mirror horizontal and rotate 270 CW
[6]={tr=true ,op='Rotate90FlipNone' ,mn='R1F0'},-- Rotate 90 CW
[7]={tr=true ,op='Rotate270FlipX' ,mn='R3FX'},-- Mirror horizontal and rotate 90 CW
[8]={tr=true ,op='Rotate270FlipNone' ,mn='R3F0'} -- Rotate 270 CW
}
local function CanReorient(image)
return image.frames==1 --TODO support reorienting animations (e.g. multipage TIFFs)
end

local function InitImage(filename)
local delete=far.ProcessName(F.PN_CMPNAMELIST,"*.dng,*.pef,*.nef,*.cr2,*.sr2,*.arw,*.orf,*.rw2,*.srw",filename,F.PN_SKIPPATH) and getDNG(filename)
local wnd=far.AdvControl(F.ACTL_GETFARHWND)
Expand All @@ -462,9 +497,9 @@ local function InitImage(filename)
gdiplus.GdipImageGetFrameDimensionsList(image[0],dimensionIDs,count[0])
local frames=ffi.new("unsigned int[1]")
gdiplus.GdipImageGetFrameCount(image[0],dimensionIDs,frames)
local delay
local delaysize=ffi.new("unsigned int[1]")
gdiplus.GdipGetPropertyItemSize(image[0],0x5100,delaysize)
local delay
if delaysize[0]>0 then
delay={}
local delayraw_buffer=ffi.new("char[?]",delaysize[0])
Expand All @@ -475,13 +510,25 @@ local function InitImage(filename)
table.insert(delay,value>0 and value or 1)
end
end
local exifOrient=0
local exifOrientSize=ffi.new("unsigned int[1]")
gdiplus.GdipGetPropertyItemSize(image[0],0x112,exifOrientSize)
if exifOrientSize[0]>0 then
local exifOrientRaw_buffer=ffi.new("char[?]",exifOrientSize[0])
local exifOrientRaw=ffi.cast("PropertyItem*",exifOrientRaw_buffer)
gdiplus.GdipGetPropertyItem(image[0],0x112,exifOrientSize[0],exifOrientRaw)
exifOrient=ffi.cast("unsigned short*",exifOrientRaw.value)[0]
end
local brush=ffi.new("void*[1]")
gdiplus.GdipCreateSolidFill(BGColor(),brush)
local memimage=ffi.new("void*[1]")
gdiplus.GdipCreateBitmapFromScan0(width[0],height[0],0,0x26200a,ffi.NULL,memimage)
local tr=RotFlipByOrient[exifOrient] and RotFlipByOrient[exifOrient].tr and CanReorient{frames=frames[0]}
local wh,hw=tr and height[0] or width[0],tr and width[0] or height[0]
gdiplus.GdipCreateBitmapFromScan0(wh,hw,0,0x26200a,ffi.NULL,memimage)
local memgraphics=ffi.new("void*[1]")
gdiplus.GdipGetImageGraphicsContext(memimage[0],memgraphics)
return {wnd=wnd,dc=dc,image=image,graphics=graphics,brush=brush,width=width[0],height=height[0],frames=frames[0],delay=delay,guid=dimensionIDs,delete=delete,memory={image=memimage,graphics=memgraphics}}
return {wnd=wnd,dc=dc,image=image,graphics=graphics,brush=brush,width=wh,height=hw,orient=exifOrient,
frames=frames[0],delay=delay,guid=dimensionIDs,delete=delete,memory={image=memimage,graphics=memgraphics}}
end
return false
end
Expand Down Expand Up @@ -533,11 +580,9 @@ local function RangingPic(params)
end

local function UpdateImage(params)
local width=params.RangedRect.right-params.RangedRect.left
local height=params.RangedRect.bottom-params.RangedRect.top
gdiplus.GdipFillRectangleI(params.image.memory.graphics[0],params.image.brush[0],0,0,params.image.width,params.image.height)
--GdipDrawImageI fails on some images
--FIXME: какая-то ересь. на некоторых картинках первый вызов GdipDrawImageRectI завершается с ошибкой.
--FIXME: какая-то ересь, на некоторых картинках первый вызов GdipDrawImageRectI завершается с ошибкой.
for _=0,1 do
if C.Win32Error~=gdiplus.GdipDrawImageRectI(params.image.memory.graphics[0],params.image.image[0],0,0,params.image.width,params.image.height) then break end
end
Expand All @@ -564,15 +609,23 @@ local function ShowImage(xpanel)
local buffer=far.CreateUserControl(width,height)
local function FillBuffer()
local color=far.AdvControl(F.ACTL_GETCOLOR,K.COL_PANELTEXT)
local textel={Char=bit64.bor(bit64.band(Symbol,0xf),0x30),Attributes={Flags=(bit64.band(color.Flags,F.FCF_BG_4BIT)==0) and 0 or bit64.bor(F.FCF_FG_4BIT,F.FCF_BG_4BIT),ForegroundColor=color.BackgroundColor,BackgroundColor=color.BackgroundColor}}
local textel={Char=bit64.bor(bit64.band(Symbol,0xf),0x30),Attributes={Flags=(bit64.band(color.Flags,F.FCF_BG_4BIT)==0) and 0
or bit64.bor(F.FCF_FG_4BIT,F.FCF_BG_4BIT),ForegroundColor=color.BackgroundColor,BackgroundColor=color.BackgroundColor}}
Symbol=Symbol+1
for ii=1,#buffer do
buffer[ii]=textel
end
end
FillBuffer()
local rotFlip=RotFlipByOrient[params.image.orient]
if rotFlip and rotFlip.mn~='R0F0' and CanReorient(params.image) then
gdiplus.GdipImageRotateFlip(params.image.image[0],rotFlip.op)
end
local orientMarker=rotFlip and ' '..rotFlip.mn or ''
if not CanReorient(params.image) then orientMarker=orientMarker:lower() end
local items={
{"DI_DOUBLEBOX",0,0,width+1,height+1,0,0,0,0,params.image.width.." x "..params.image.height.." * "..params.image.frames},
{'DI_DOUBLEBOX',0,0,width+1,height+1,0,0,0,0,
params.image.width..' x '..params.image.height..' * '..params.image.frames..orientMarker},
{"DI_USERCONTROL",1,1,width,height,buffer,0,0,0,""}
}
local function DlgProc(dlg,msg,param1,param2)
Expand All @@ -592,7 +645,7 @@ local function ShowImage(xpanel)
end
end
if msg==F.DN_INITDIALOG then
far.SendDlgMessage (dlg,F.DM_SETMOUSEEVENTNOTIFY,1)
far.SendDlgMessage(dlg,F.DM_SETMOUSEEVENTNOTIFY,1)
if params.image.frames>1 then
local function ShowAnimation()
if params.timer and not params.timer.Closed then params.timer.Enabled=false end
Expand Down

0 comments on commit 600bb53

Please sign in to comment.