ImagePlugin Module (Cross platform multi format de-/encoder)

Share your advanced PureBasic knowledge/code with the community.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

ImagePlugin Module (Cross platform multi format de-/encoder)

Post by wilbert »

ImagePlugin Module (Cross platform multi format de-/encoder)

This module adds support of multiple image formats to CatchImage, LoadImage, EncodeImage and SaveImage.
In general the Depth argument to specify the color depth is ignored.

v1.52 contains a fix for setting jpeg quality on PB x86 for Windows.

Platform specifics

Windows
Special requirements : MemoryStream module ( http://www.purebasic.fr/english/viewtop ... =5&t=66487 ), gdiplus.lib
Supported decode formats : JPEG, GIF, TIFF, PNG
Supported encode formats : BMP, JPEG, GIF, TIFF, PNG

Mac
Supported decode formats : JPEG, GIF, TIFF, PNG, JPEG2000
Supported encode formats : BMP, JPEG, GIF, TIFF, PNG

Linux
Supported decode formats : JPEG, GIF, TIFF, PNG
Supported encode formats : BMP, JPEG, TIFF, PNG

Image format specifics

JPEG
Encoding quality can be set from 1 - 100.
SaveImage(0, "output.jpg", #SystemImagePlugin, #SystemImagePlugin_JPEG | 70)

GIF
Encoded image might be dithered.
Encoding is not supported by default on Linux.

TIFF
Optional Depth argument for EncodeImage and SaveImage is supported on Windows.

Code: Select all

;================================================;
; Module      : ImagePlugin (Cross platform)     ;
; Author      : Wilbert                          ;
; Date        : Sep 9, 2016                      ;
; Version     : 1.52                             ;
;                                                ;
; Implemented : ModuleImagePluginStop()          ;
;               UseSystemImageDecoder()          ;
;               UseSystemImageEncoder()          ;
;                                                ;
; *Additional requirements for Windows:          ;
;  MemoryStreamModule.pbi, gdiplus.lib           ;
;================================================;


;- Module declaration

DeclareModule ImagePlugin
  
  #SystemImagePlugin           = $737953
  #SystemImagePlugin_BMP       = $100000
  #SystemImagePlugin_JPEG      = $300100
  #SystemImagePlugin_GIF       = $200200
  #SystemImagePlugin_TIFF_LZW  = $052500
  #SystemImagePlugin_TIFF_None = $016500
  #SystemImagePlugin_PNG       = $400600
  
  Declare   ModuleImagePluginStop()
  Declare.i UseSystemImageDecoder()
  Declare.i UseSystemImageEncoder()
  
EndDeclareModule


;- Module implementation

CompilerIf #PB_Compiler_OS = #PB_OS_Windows And Not Defined(MemoryStream, #PB_Module)
  ; http://www.purebasic.fr/english/viewtopic.php?f=5&t=66487
  IncludeFile "MemoryStreamModule.pbi"
CompilerEndIf

Module ImagePlugin
  
  EnableExplicit
  DisableDebugger
  
  ;- Structures (all OS)
  
  Structure PB_ImageDecoder Align #PB_Structure_AlignC
    *Check
    *Decode
    *Cleanup
    ID.l
  EndStructure
  
  Structure PB_ImageDecoderGlobals Align #PB_Structure_AlignC
    *Decoder.PB_ImageDecoder
    *Filename
    *File
    *Buffer
    Length.l
    Mode.l
    Width.l
    Height.l
    Depth.l
    Flags.l
    Data.i[8]
    OriginalDepth.l
  EndStructure
  
  Structure PB_ImageEncoder Align #PB_Structure_AlignC
    ID.l
    *Encode24
    *Encode32
  EndStructure
  
  ;- Imports (all OS)
  
  IsImage(0) ; make sure ImagePlugin library is available so imports won't fail
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows And #PB_Compiler_Processor = #PB_Processor_x86  
    Import ""
      PB_ImageDecoder_Register(*ImageDecoder.PB_ImageDecoder) As "_PB_ImageDecoder_Register@4"
      PB_ImageEncoder_Register(*ImageEncoder.PB_ImageEncoder) As "_PB_ImageEncoder_Register@4"
    EndImport
  CompilerElse
    ImportC "" ; ImportC can be used on all OS for PB x64
      PB_ImageDecoder_Register(*ImageDecoder.PB_ImageDecoder)
      PB_ImageEncoder_Register(*ImageEncoder.PB_ImageEncoder)
    EndImport
  CompilerEndIf
  
  ; Private procedures (all OS)
  
  Procedure.i JPEGQuality(EncoderFlags.l)
    Protected Value.l = EncoderFlags & $FF
    If Value = 0
      Value = 80
    ElseIf Value > 100
      Value = 100
    EndIf
    ProcedureReturn Value
  EndProcedure
    
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    
    ;{ >>> WINDOWS <<<
    
    ;- Structures (Windows)
    
    Structure GdiplusStartupInput_
      GdiPlusVersion.i
      *DebugEventCallback
      SuppressBackgroundThread.l
      SuppressExternalCodecs.l 
    EndStructure
    
    Structure BitmapData_
      Width.l
      Height.l
      Stride.l
      PixelFormat.l
      *Scan0
      *Reserved
    EndStructure
    
    Structure EncoderParameter_
      Guid.q[2]
      NumberOfValues.l
      Type.l
      *Value
    EndStructure
    
    Structure EncoderParameters_
      Count.i
      Parameter.EncoderParameter_[2]
    EndStructure
    
    ;- Imports (Windows)
    
    Import "gdiplus.lib"
      GdipBitmapLockBits(bitmap, *rect, flags, format, *lockedBitmapData)
      GdipBitmapUnlockBits(bitmap, *lockedBitmapData)
      GdipCreateBitmapFromFile(filename.p-unicode, *bitmap)
      GdipCreateBitmapFromScan0(width, height, stride, format, *scan0, *bitmap)
      GdipCreateBitmapFromStream(stream, *bitmap)
      GdipDeleteGraphics(graphics)
      GdipDisposeImage(image)
      GdipDrawImageI(graphics, image, x, y)
      GdipGetImageHeight(image, *height)
      GdipGetImagePixelFormat(image, *format)
      GdipGetImageWidth(image, *width)
      GdipGetImageGraphicsContext(image, *graphics)
      GdipSaveImageToFile(image, filename.p-unicode, *clsidEncoder, *encoderParams)
      GdipSaveImageToStream(image, stream, *clsidEncoder, *encoderParams)
      GdiplusShutdown(token)
      GdiplusStartup(*token, *input, *output) 
    EndImport
    
    ;- Macros (Windows)
    
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
      Macro rax : eax : EndMacro
      Macro rbx : ebx : EndMacro
      Macro rcx : ecx : EndMacro
      Macro rdx : edx : EndMacro
    CompilerEndIf  
    
    ;- Global variables (Windows)
    
    Global Input.GdiplusStartupInput_, Token.i
    
    ;- Private procedures (Windows)
    
    Procedure _PNGSizeCheck_(*MemoryAddress, MaxSize.l)
      EnableASM
      mov rdx, [p.p_MemoryAddress]
      !mov eax, [p.v_MaxSize]
      push rbx
      !lea ebx, [eax - 8]
      !xor eax, eax
      cmp dword [rdx], 0x474e5089
      !jne .l1
      cmp dword [rdx + 4], 0x0a1a0a0d
      !jne .l1
      !mov eax, 8
      !.l0:
      mov ecx, [rdx + rax]
      !bswap ecx
      lea rax, [rax + rcx + 12]
      !cmp eax, ebx
      !ja .l1
      cmp dword [rdx + rax + 4], 0x444e4549  
      !jne .l0
      !add eax, 12
      !.l1:
      pop rbx
      DisableASM
      ProcedureReturn
    EndProcedure
    
    Procedure.i _Start_()
      If Token = 0
        Input\GdiPlusVersion = 1
        GdiplusStartup(@Token, @Input, #Null)
      EndIf
      ProcedureReturn Token
    EndProcedure
    
    Procedure   _Cleanup_(*Globals.PB_ImageDecoderGlobals)
      If *Globals\Data[0]
        GdipDisposeImage(*Globals\Data[0]) : *Globals\Data[0] = #Null
      EndIf  
    EndProcedure
    
    Procedure.i _Check_(*Globals.PB_ImageDecoderGlobals)
      
      Protected Size.l, Stream.IStream
      
      If Token
        If *Globals\Mode = 0
          ; File Mode
          GdipCreateBitmapFromFile(PeekS(*Globals\FileName), @*Globals\Data[0])
        Else
          ; Memory Mode
          Size = _PNGSizeCheck_(*Globals\Buffer, *Globals\Length)
          If Size And Size < *Globals\Length
            *Globals\Length = Size
          EndIf
          Stream = MemoryStream::CreateMemoryStream(MemoryStream::#MemoryStream_ReadOnly, *Globals\Buffer, *Globals\Length)
          GdipCreateBitmapFromStream(Stream, @*Globals\Data[0])
          Stream\Release()
        EndIf
        If *Globals\Data[0]
          GdipGetImageWidth(*Globals\Data[0], @*Globals\Width) 
          GdipGetImageHeight(*Globals\Data[0], @*Globals\Height)
          GdipGetImagePixelFormat(*Globals\Data[0], @*Globals\OriginalDepth)
          *Globals\OriginalDepth = *Globals\OriginalDepth >> 8 & $FF
          *Globals\Depth = 32
          ProcedureReturn #True
        EndIf
      EndIf
      ProcedureReturn #False
      
    EndProcedure
    
    Procedure.i _Decode_(*Globals.PB_ImageDecoderGlobals, *Buffer, Pitch.l, Flags.l)
      
      Protected Rect.Rect, BitmapData.BitmapData_
      
      Rect\right              = *Globals\Width
      Rect\bottom             = *Globals\Height
      BitmapData\Width        = *Globals\Width
      BitmapData\Height       = *Globals\Height
      If Flags & 2            ; ReverseY ?
        BitmapData\Stride     = -Pitch
        BitmapData\Scan0      = *Buffer + (*Globals\Height - 1) * Pitch
      Else
        BitmapData\Stride     = Pitch
        BitmapData\Scan0      = *Buffer
      EndIf
      BitmapData\PixelFormat  = $26200A
      GdipBitmapLockBits(*Globals\Data[0], @Rect, 5, $26200A, @BitmapData)
      GdipBitmapUnlockBits(*Globals\Data[0], @BitmapData)
      _Cleanup_(*Globals)
      ProcedureReturn #True
      
    EndProcedure
    
    Procedure.i _Encode_(PixelFormat, *Filename, *Buffer, Width.l, Height.l, LinePitch.l, Flags.l, EncoderFlags.l, RequestedDepth.l)
      
      Protected.i Result, Bitmap, ImageType, TBuffer, TBitmap, Graphics, Value, Size.q, Stream.IStream
      Protected Parameters.EncoderParameters_, *Parameters = @Parameters
      Protected Dim CLSID.q(1)
      
      If Token
        If Flags & 2
          GdipCreateBitmapFromScan0(Width, Height, -LinePitch, PixelFormat, *Buffer + (Height - 1) * LinePitch, @Bitmap)
        Else
          GdipCreateBitmapFromScan0(Width, Height, LinePitch, PixelFormat, *Buffer, @Bitmap)
        EndIf
        If Bitmap
          ImageType = EncoderFlags >> 8 & 7
          If PixelFormat = $26200A And ImageType < 2
            ; Try to flatten alpha channel for BMP and JPEG
            TBuffer = AllocateMemory(Width << 2 * Height, #PB_Memory_NoClear)
            If TBuffer
              FillMemory(TBuffer, Width << 2 * Height, $FFFFFFFF, #PB_Long)
              If GdipCreateBitmapFromScan0(Width, Height, Width << 2, PixelFormat, TBuffer, @TBitmap) = 0
                If GdipGetImageGraphicsContext(TBitmap, @Graphics) = 0
                  GdipDrawImageI(Graphics, Bitmap, 0, 0)
                  GdipDeleteGraphics(Graphics)
                  GdipDisposeImage(Bitmap) : Bitmap = TBitmap
                EndIf
              EndIf
            EndIf
          EndIf
          Select ImageType
            Case 1  ; JPEG
              Value = JPEGQuality(EncoderFlags)
              Parameters\Count = 1
              Parameters\Parameter[0]\Guid[0] = $452DFA4A1D5BE4B5
              Parameters\Parameter[0]\Guid[1] = $EBE70551B35DDD9C
              Parameters\Parameter[0]\NumberOfValues = 1
              Parameters\Parameter[0]\Type = 4
              Parameters\Parameter[0]\Value = @Value
            Case 5  ; TIFF
              Value = EncoderFlags >> 12 & 7
              Parameters\Count = 2
              Parameters\Parameter[0]\Guid[0] = $44EECCD4E09D739D
              Parameters\Parameter[0]\Guid[1] = $58FCE48BBF3FBA8E
              Parameters\Parameter[0]\NumberOfValues = 1
              Parameters\Parameter[0]\Type = 4
              Parameters\Parameter[0]\Value = @Value
              Parameters\Parameter[1]\Guid[0] = $4C7CAD6666087055
              Parameters\Parameter[1]\Guid[1] = $37830B31A238189A
              Parameters\Parameter[1]\NumberOfValues = 1
              Parameters\Parameter[1]\Type = 4
              Parameters\Parameter[1]\Value = @RequestedDepth
            Default
              *Parameters = #Null
          EndSelect
          CLSID(0) = $11D31A04557CF400 + ImageType : CLSID(1) = $2EF31EF80000739A
          If *Filename
            ; File Mode
            Result = Bool(GdipSaveImageToFile(Bitmap, PeekS(*Filename), @CLSID(), *Parameters) = 0)
          Else
            ; Memory Mode
            If CreateStreamOnHGlobal_(#Null, #True, @Stream) = 0; Create empty stream which can grow
              If GdipSaveImageToStream(Bitmap, Stream, @CLSID(), *Parameters) = 0
                Stream\Seek(0, #STREAM_SEEK_END, @Size); Check stream size after saving to stream 
                If Size > 0
                  Result = AllocateMemory(Size, #PB_Memory_NoClear)
                  If Result
                    Stream\Seek(0, #STREAM_SEEK_SET, #Null)
                    Stream\Read(Result, Size, #Null); Copy encoded image to allocated memory
                  EndIf
                EndIf
              EndIf
              Stream\Release()  
            EndIf
          EndIf
          GdipDisposeImage(Bitmap)  
        EndIf
      EndIf
      If TBuffer
        FreeMemory(TBuffer)  
      EndIf
      ProcedureReturn Result
      
    EndProcedure
    
    Procedure.i _Encode24_(*Filename, *Buffer, Width.l, Height.l, LinePitch.l, Flags.l, EncoderFlags.l, RequestedDepth.l)
      ProcedureReturn _Encode_($21808, *Filename, *Buffer, Width, Height, LinePitch, Flags, EncoderFlags, RequestedDepth)
    EndProcedure
    
    Procedure.i _Encode32_(*Filename, *Buffer, Width.l, Height.l, LinePitch.l, Flags.l, EncoderFlags.l, RequestedDepth.l)
      ProcedureReturn _Encode_($26200A, *Filename, *Buffer, Width, Height, LinePitch, Flags, EncoderFlags, RequestedDepth)
    EndProcedure
    
    ;}
    
  CompilerElseIf #PB_Compiler_OS = #PB_OS_MacOS
    
    ;{ >>> MAC <<<    
    
    ;- Global variables (Mac)
    
    Global.i NSImageCompressionFactor, NSImageCompressionMethod
    
    ;- Imports (Mac)
    
    ImportC -framework Accelerate"
      dlsym(handle, symbol.p-utf8)
      strlen(*s)
      vImageUnpremultiplyData_RGBA8888(*src, *dest, flags) 
      CFDataGetBytePtr(theData)
      CFRelease(cf)
      CFURLCreateFromFileSystemRepresentation(allocator, *buffer, bufLen, isDirectory)
      CGBitmapContextCreate(*data, width, height, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo)
      CGColorSpaceCreateDeviceRGB()
      CGColorSpaceRelease(colorspace)
      CGContextDrawImage(c, xf.f, yf.f, wf.f, hf.f, image, d0.f, d1.f, d2.f, d3.f, xd.d, yd.d, wd.d, hd.d)
      CGContextRelease(context)
      CGDataProviderCopyData(provider)
      CGDataProviderCreateWithData(*info, *data, size, releaseData)
      CGDataProviderRelease(provider)
      CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, provider, *decode, shouldInterpolate, intent)
      CGImageGetBitmapInfo(image)
      CGImageGetBitsPerPixel(image)
      CGImageGetBytesPerRow(image)
      CGImageGetDataProvider(image)
      CGImageGetHeight(image)
      CGImageGetWidth(image)
      CGImageRelease(image)
      CGImageSourceCreateImageAtIndex(isrc, index, options)
      CGImageSourceCreateWithDataProvider(provider, options)
      CGImageSourceCreateWithURL(url, options)      
    EndImport
    
    ;- Private procedures (Mac)
    
    ProcedureC.i _Start_()
      Static Started.i
      If Not Started
        NSImageCompressionFactor  = PeekI(dlsym(-2, "NSImageCompressionFactor"))
        NSImageCompressionMethod  = PeekI(dlsym(-2, "NSImageCompressionMethod"))
        Started = #True  
      EndIf
      ProcedureReturn #True  
    EndProcedure
    
    ProcedureC   _Cleanup_(*Globals.PB_ImageDecoderGlobals)
      If *Globals\Data[0]
        CGImageRelease(*Globals\Data[0]) : *Globals\Data[0] = #Null
      EndIf      
    EndProcedure
    
    ProcedureC.i _Check_(*Globals.PB_ImageDecoderGlobals)
      
      Protected.i BitmapInfo, Image, ImageSource, Properties, Provider, URL
      
      If *Globals\Mode = 0
        ; File Mode
        URL = CFURLCreateFromFileSystemRepresentation(#Null, *Globals\Filename, strlen(*Globals\Filename), #False)
        ImageSource = CGImageSourceCreateWithURL(URL, #Null)
        CFRelease(URL)
      Else
        ; Memory Mode
        Provider = CGDataProviderCreateWithData(#Null, *Globals\Buffer, *Globals\Length, #Null)
        ImageSource = CGImageSourceCreateWithDataProvider(Provider, #Null)
        CGDataProviderRelease(Provider)
      EndIf
      If ImageSource
        Image = CGImageSourceCreateImageAtIndex(ImageSource, 0, #Null)
        CFRelease(ImageSource)
        If Image
          BitmapInfo = CGImageGetBitmapInfo(Image)
          If BitmapInfo = 3 And CGImageGetBitsPerPixel(Image) = 32
            *Globals\Data[2] = CGImageGetBytesPerRow(Image)
          Else
            *Globals\Data[2] = 0
          EndIf
          BitmapInfo & $1F
          If BitmapInfo > 0 And BitmapInfo < 5
            *Globals\Data[1] = #True
            *Globals\OriginalDepth = 32
          Else
            *Globals\Data[1] = #False
            *Globals\OriginalDepth = 24
          EndIf
          *Globals\Data[0] = Image
          *Globals\Width = CGImageGetWidth(Image)
          *Globals\Height = CGImageGetHeight(Image)
          *Globals\Depth = 32
          ProcedureReturn #True
        EndIf
      EndIf
      ProcedureReturn #False
      
    EndProcedure
    
    ProcedureC.i _Decode_(*Globals.PB_ImageDecoderGlobals, *Buffer, Pitch.l, Flags.l)
      
      Protected.i ColorSpace, Context, Source
      Protected Dim vImg.i(3)
      
      If *Globals\Data[2] = Pitch
        Source = CGDataProviderCopyData(CGImageGetDataProvider(*Globals\Data[0]))
      EndIf
      If Source
        CopyMemory(CFDataGetBytePtr(Source), *Buffer, *Globals\Height * Pitch)
        CFRelease(Source)
      Else
        ColorSpace = CGColorSpaceCreateDeviceRGB()
        Context = CGBitmapContextCreate(*Buffer, *Globals\Width, *Globals\Height, 8, Pitch, ColorSpace, 1)
        CGContextDrawImage(Context, 0, 0, *Globals\Width, *Globals\Height, *Globals\Data[0], 0,0,0,0, 0, 0, *Globals\Width, *Globals\Height)
        CGContextRelease(Context)
        CGColorSpaceRelease(ColorSpace)
        If *Globals\Data[1]; Unpremultiply when original image had an alpha channel
          vImg(0) = *Buffer
          vImg(1) = *Globals\Height
          vImg(2) = *Globals\Width
          vImg(3) = Pitch
          vImageUnPremultiplyData_RGBA8888(@vImg(), @vImg(), 0)
        EndIf
      EndIf
      _Cleanup_(*Globals)
      ProcedureReturn #True
      
    EndProcedure
    
    ProcedureC.i _Encode_(Depth.l, *Filename, *Buffer, Width.l, Height.l, LinePitch.l, Flags.l, EncoderFlags.l, RequestedDepth.l)
      
      Protected.i Provider, ColorSpace, Image, ImageRep, Pool, ImageType, Props, ImageData, Length, Result
      Protected FileName.s, Quality.f
      
      Provider = CGDataProviderCreateWithData(#Null, *Buffer, Height * LinePitch, #Null)
      ColorSpace = CGColorSpaceCreateDeviceRGB()
      If Depth = 32
        Image = CGImageCreate(Width, Height, 8, 32, LinePitch, ColorSpace, 3, Provider, #Null, #False, 0)
      Else
        Image = CGImageCreate(Width, Height, 8, 24, LinePitch, ColorSpace, 0, Provider, #Null, #False, 0)
      EndIf
      ImageRep = CocoaMessage(0, CocoaMessage(0, 0, "NSBitmapImageRep alloc"), "initWithCGImage:", Image)
      CGImageRelease(Image)
      CGColorSpaceRelease(ColorSpace)
      CGDataProviderRelease(Provider)
      If ImageRep
        Pool = CocoaMessage(0, 0, "NSAutoreleasePool new")
        ImageType = EncoderFlags >> 20 & 7
        Select ImageType
          Case 0  ; TIFF
            Props = CocoaMessage(0, 0, "NSDictionary dictionaryWithObject:",
                                 CocoaMessage(0, 0, "NSNumber numberWithInt:", EncoderFlags >> 16 & 7),
                                 "forKey:", NSImageCompressionMethod)
          Case 3  ; JPEG
            Quality = 0.01 * JPEGQuality(EncoderFlags)
            Props = CocoaMessage(0, 0, "NSDictionary dictionaryWithObject:",
                                 CocoaMessage(0, 0, "NSNumber numberWithFloat:@", @Quality),
                                 "forKey:", NSImageCompressionFactor)
        EndSelect
        ImageData = CocoaMessage(0, ImageRep, "representationUsingType:", ImageType, "properties:", Props)
        If ImageData
          If *Filename
            ; File Mode
            FileName = PeekS(*Filename, -1, #PB_UTF8)
            Result = CocoaMessage(0, ImageData, "writeToFile:$", @FileName, "atomically:", #NO)
          Else
            ; Memory Mode
            Length = CocoaMessage(0, ImageData, "length")
            Result = AllocateMemory(Length, #PB_Memory_NoClear)
            CocoaMessage(0, ImageData, "getBytes:", Result, "length:", Length)
          EndIf
        EndIf
        CocoaMessage(0, Pool, "release")
        CocoaMessage(0, ImageRep, "release")
      EndIf
      
      ProcedureReturn Result
      
    EndProcedure
    
    ProcedureC.i _Encode24_(*Filename, *Buffer, Width.l, Height.l, LinePitch.l, Flags.l, EncoderFlags.l, RequestedDepth.l)
      ProcedureReturn _Encode_(24, *Filename, *Buffer, Width, Height, LinePitch, Flags, EncoderFlags, RequestedDepth)
    EndProcedure
    
    ProcedureC.i _Encode32_(*Filename, *Buffer, Width.l, Height.l, LinePitch.l, Flags.l, EncoderFlags.l, RequestedDepth.l)
      ProcedureReturn _Encode_(32, *Filename, *Buffer, Width, Height, LinePitch, Flags, EncoderFlags, RequestedDepth)
    EndProcedure
    
    ;}
    
  CompilerElseIf #PB_Compiler_OS = #PB_OS_Linux
    
    ;{ >>> LINUX <<<  
    
    ;- Imports (Linux)
    
    ImportC ""
      free(ptr)
      g_object_ref(object)
      g_object_unref(object)
      gdk_pixbuf_composite_color_simple(src, dest_width, dest_height, interp_type,
                                        overall_alpha, check_size, color1, color2)
      gdk_pixbuf_copy_area(src, src_x, src_y, width, height, dest, dest_x, dest_y)
      gdk_pixbuf_get_bits_per_sample(pixbuf)
      gdk_pixbuf_get_height(pixbuf)
      gdk_pixbuf_get_n_channels(pixbuf)
      gdk_pixbuf_get_width(pixbuf)
      gdk_pixbuf_loader_close(loader, *error)
      gdk_pixbuf_loader_get_pixbuf(loader)
      gdk_pixbuf_loader_new()
      gdk_pixbuf_loader_write(loader, buf, count, *error)
      gdk_pixbuf_new_from_data(*data, colorspace, has_alpha, bits_per_sample,
                               width, height, rowstride, destroy_fn, destroy_fn_data)
      gdk_pixbuf_new_from_file(*filename, *error)
      gdk_pixbuf_save(pixbuf, *filename, type.p-ascii, *error, param.p-ascii, value.p-ascii, null = 0)
      gdk_pixbuf_save_to_buffer(pixbuf, *buffer, *buffer_size, type.p-ascii, *error, param.p-ascii, value.p-ascii, null = 0)
    EndImport
    
    ;- Global variables (Linux)
    
    Global Dim Type.s(7)
    
    ;- Private procedures (Linux)
    
    ProcedureC.i _Start_()
      Static Started.i
      If Not Started
        Type(0) = "tiff" : Type(1) = "bmp" : Type(2) = "gif" : Type(3) = "jpeg" : Type(4) = "png"
        Started = #True  
      EndIf
      ProcedureReturn #True  
    EndProcedure
    
    ProcedureC   _Cleanup_(*Globals.PB_ImageDecoderGlobals)
      If *Globals\Data[0]
        g_object_unref(*Globals\Data[0]) : *Globals\Data[0] = #Null
      EndIf
    EndProcedure
    
    ProcedureC.i _Check_(*Globals.PB_ImageDecoderGlobals)
      Protected.i Loader, PixBuf
      If *Globals\Mode = 0
        ; File Mode
        PixBuf = gdk_pixbuf_new_from_file(*Globals\Filename, #Null)
      Else
        ; Memory Mode
        Loader = gdk_pixbuf_loader_new()
        gdk_pixbuf_loader_write(Loader, *Globals\Buffer, *Globals\length, #Null)
        gdk_pixbuf_loader_close(Loader, #Null)
        PixBuf = gdk_pixbuf_loader_get_pixbuf(Loader)
        If PixBuf : g_object_ref(PixBuf) : EndIf
        g_object_unref(Loader)
      EndIf
      If PixBuf
        *Globals\Data[0] = PixBuf
        *Globals\Width = gdk_pixbuf_get_width(PixBuf)
        *Globals\Height = gdk_pixbuf_get_height(PixBuf)
        *Globals\OriginalDepth = gdk_pixbuf_get_bits_per_sample(PixBuf) * gdk_pixbuf_get_n_channels(PixBuf)
        *Globals\Depth = 32
        ProcedureReturn #True
      EndIf
      ProcedureReturn #False
    EndProcedure
    
    ProcedureC.i _Decode_(*Globals.PB_ImageDecoderGlobals, *Buffer, Pitch, Flags)
      Protected.i Result, PixBuf
      PixBuf = gdk_pixbuf_new_from_data(*Buffer, 0, #True, 8, *Globals\Width, *Globals\Height, Pitch, #Null, 0) 
      If PixBuf
        gdk_pixbuf_copy_area(*Globals\Data[0], 0, 0, *Globals\Width, *Globals\Height, PixBuf, 0, 0)
        g_object_unref(PixBuf)
        Result = #True
      EndIf
      _Cleanup_(*Globals)
      ProcedureReturn Result
    EndProcedure
    
    ProcedureC.i _Encode_(HasAlpha, *Filename, *Buffer, Width.l, Height.l, LinePitch.l, Flags.l, EncoderFlags.l, RequestedDepth.l)
      Protected.i Result, PixBuf, TPixBuf, ImageType, Error, TBuffer, TBuffer_Size
      Protected.s Param, Value
      PixBuf = gdk_pixbuf_new_from_data(*Buffer, 0, HasAlpha, 8, Width, Height, LinePitch, #Null, 0) 
      If PixBuf
        ImageType = EncoderFlags >> 20 & 7
        If HasAlpha And (ImageType = 1 Or ImageType = 3)
          ; Try to flatten alpha channel for BMP and JPEG 
          TPixBuf = gdk_pixbuf_composite_color_simple(PixBuf, Width, Height, 0, 255, 16, $FFFFFFFF, $FFFFFFFF)
          If TPixBuf
            g_object_unref(PixBuf) : PixBuf = TPixBuf
          EndIf
        EndIf
        Select ImageType
          Case 0  ; TIFF
            Param = "compression" : Value = Str(EncoderFlags >> 16 & 7)
          Case 3  ; JPEG
            Param = "quality" : Value = Str(JPEGQuality(EncoderFlags))
        EndSelect
        If *Filename
          ; File mode
          Result = gdk_pixbuf_save(PixBuf, *Filename, Type(ImageType), @Error, Param, Value) 
        Else
          ; Memory mode
          If gdk_pixbuf_save_to_buffer(PixBuf, @TBuffer, @TBuffer_Size, Type(EncoderFlags >> 20 & 7), @Error, Param, Value) 
            Result = AllocateMemory(TBuffer_Size, #PB_Memory_NoClear)
            If Result
              CopyMemory(TBuffer, Result, TBuffer_Size)  
            EndIf
            free(TBUffer)
          EndIf
        EndIf
        g_object_unref(PixBuf)
      EndIf
      ProcedureReturn Result
    EndProcedure
    
    ProcedureC.i _Encode24_(*Filename, *Buffer, Width.l, Height.l, LinePitch.l, Flags.l, EncoderFlags.l, RequestedDepth.l)
      ProcedureReturn _Encode_(#False, *Filename, *Buffer, Width, Height, LinePitch, Flags, EncoderFlags, RequestedDepth)
    EndProcedure
    
    ProcedureC.i _Encode32_(*Filename, *Buffer, Width.l, Height.l, LinePitch.l, Flags.l, EncoderFlags.l, RequestedDepth.l)
      ProcedureReturn _Encode_(#True, *Filename, *Buffer, Width, Height, LinePitch, Flags, EncoderFlags, RequestedDepth)
    EndProcedure
    
    ;}
    
  CompilerEndIf    
  
  ;- Public procedures (all OS)
  
  Procedure ModuleImagePluginStop()
    CompilerIf #PB_Compiler_OS = #PB_OS_Windows
      If Token
        GdiplusShutdown(Token) : Token = 0
      EndIf
    CompilerEndIf
  EndProcedure
  
  Procedure.i UseSystemImageDecoder()
    Static SystemImageDecoder.PB_ImageDecoder, Registered.i
    If _Start_() And Registered = #False
      SystemImageDecoder\ID       = #SystemImagePlugin
      SystemImageDecoder\Check    = @_Check_()
      SystemImageDecoder\Cleanup  = @_Cleanup_()
      SystemImageDecoder\Decode   = @_Decode_()
      PB_ImageDecoder_Register(SystemImageDecoder)
      Registered = #True
    EndIf
    ProcedureReturn Registered
  EndProcedure
  
  Procedure.i UseSystemImageEncoder()
    Static SystemImageEncoder.PB_ImageEncoder, Registered.i
    If _Start_() And Registered = #False
      SystemImageEncoder\ID       = #SystemImagePlugin
      SystemImageEncoder\Encode24 = @_Encode24_()
      SystemImageEncoder\Encode32 = @_Encode32_()
      PB_ImageEncoder_Register(SystemImageEncoder)
      Registered = #True
    EndIf
    ProcedureReturn Registered
  EndProcedure
  
EndModule
Last edited by wilbert on Thu Mar 25, 2021 3:00 pm, edited 17 times in total.
Windows (x64)
Raspberry Pi OS (Arm64)
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by wilbert »

Decoder example

Code: Select all

DataSection
  Image:
  IncludeBinary "test.gif"
  Image_End:
EndDataSection

UseModule ImagePlugin
UseSystemImageDecoder()

CatchImage(0, ?Image, ?Image_End - ?Image)

OpenWindow(0, 0, 0, 500, 400, "Image plugin example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ImageGadget(0, 0, 0, 500, 400, ImageID(0))
Repeat
  Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow

ModuleImagePluginStop()
Encoder example

Code: Select all

UseModule ImagePlugin
UseSystemImageEncoder()

CreateImage(0, 256, 256, 24, $00ff00)

; Encode as LZW compressed TIFF
*Mem = EncodeImage(0, #SystemImagePlugin, #SystemImagePlugin_TIFF_LZW)

; Save as jpeg with quality 70
SaveImage(0, "test.jpg", #SystemImagePlugin, #SystemImagePlugin_JPEG | 70)

ModuleImagePluginStop()
Last edited by wilbert on Mon Sep 05, 2016 11:12 am, edited 3 times in total.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
J. Baker
Addict
Addict
Posts: 2178
Joined: Sun Apr 27, 2003 8:12 am
Location: USA
Contact:

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by J. Baker »

Works great on XP wilbert! :D

Thanks! ;)
www.posemotion.com

PureBasic Tools for OS X: PureMonitor, plist Tool, Data Maker & App Chef

Mac: 10.13.6 / 1.4GHz Core 2 Duo / 2GB DDR3 / Nvidia 320M
PC: Win 7 / AMD 64 4000+ / 3GB DDR / Nvidia 720GT


Even the vine knows it surroundings but the man with eyes does not.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by Keya »

working fine here on old XP-32! your demo compiles to just 54kb too so it seems like another really nice way to get transparency without the ~200kb overhead of the PNG codec :) and i havent been able to try them yet but it looks like you've got them up and running on Linux and OSX too so fully multi-OS?!! :)
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by wilbert »

Thanks for testing :)
Keya wrote:it seems like another really nice way to get transparency without the ~200kb overhead of the PNG codec :)
Yes, transparent PNG is supported as well :wink:
Keya wrote:it looks like you've got them up and running on Linux and OSX too so fully multi-OS?!! :)
Yes, all three work in a more or less similar way (as usual every OS had / has it's own challenges).
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8433
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by netmaestro »

Works fine here on Win7 x64 running PB 5.50 x86. Nice to see that gdiplus.lib is now included in the Windows lib folder of PureBasic, I wonder how many versions it's been sitting there and I didn't know it. Were you considering covering the Encoder side at all, or just leave it at decoding? Anyway, very nice clean implementation. If you pm Fred he may be able to show you how to tap into PB's cleanup routines with a view to doing away with that one thorn in your side: UseWindowsImageDecoder(#False).
BERESHEIT
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by walbus »

@wilbert
As always, a very usefull code from you !
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by wilbert »

netmaestro wrote:Were you considering covering the Encoder side at all, or just leave it at decoding?
If anybody is interested in an encoder, I could try to add that as well.
It could be a single plugin supporting multiple formats with the format being specified by the flags argument or a separate plugin for each desired format.
netmaestro wrote:Anyway, very nice clean implementation. If you pm Fred he may be able to show you how to tap into PB's cleanup routines with a view to doing away with that one thorn in your side: UseWindowsImageDecoder(#False).
Thanks :)
If there's any way to automatically call a cleanup procedure for a module that would be a great improvement.
I'll ask Fred if there's any way at the moment to register some kind of callback.
Windows (x64)
Raspberry Pi OS (Arm64)
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by walbus »

Hi wilbert,
a encoder was a great thing.
Important is Tiff with lossles compression, best is LZW, i think.

Your decoder is also important for fixing the PB Win PNG bug.

(For other users, a little hint:)
https://havecamerawilltravel.com/photog ... ompression
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5353
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by Kwai chang caine »

Forthwith say..forthwith do 8)
Thanks a lot, for try to adding, this great missing of PB 8)

Obviously, when i see your title, immediately i test your code, i don't know if you know why and what is the extension i test ? :mrgreen:
But i have run your code, the image appears, but she is not animated
Image
I test another one and it's the same punition, is it normal ?? :|
ImageThe happiness is a road...
Not a destination
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by walbus »

@Kwai Chang Caine
I have given up long ago to try each code to understand -LOL
Wilbert codes are perfect.
Without Wilbert codes my own apps are not the same.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5353
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by Kwai chang caine »

In fact for this time, even not i try to understand the not understandable for me (too much complex :oops:)
Just want see all my "kcc baby GIF" move, move and move again and always.... :D
But here...my GIF is figed like if he have see the wolf :shock: :lol:
Wilbert codes are perfect.
Without Wilbert codes my own apps are not the same.
I know !!!
WILBERT is not a programmer....it's a magician.... a god of bit... he have save me several time, and create jewel for me... i love him 8) 8)
But too much far of me for maried :mrgreen:
ImageThe happiness is a road...
Not a destination
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by walbus »

You are OK Kwai chang caine.

Nobody has anything to.

Tiff enhancement, loading and saving, is, i think, "a missing link" in PB
Also a bug free PNG support for Win PB.

For handling pictures Tiff is a standard.
Tiff should supporting Zip and LZW compression.
But not all software supporting Tiff with LZW to time...
With LZW jou become mostly better compression results.

PNG is for me not chicken and not egg, a strange and internal complicate format, i self think.

PNG should even replace Gif, but so far not, and i self think, it come not.
Last edited by walbus on Thu Sep 01, 2016 11:00 am, edited 1 time in total.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by Keya »

if you display a single framed image, like a bitmap, you just set the buffer and it's done. The problem however for an animated GIF is it needs a worker thread to keep updating it or at least a timer, and I don't think the OS-based API like GDI handle that? and even if it did it would surely result in a worker thread/timer, and you'd probably also want a way to gracefully close that thread/timer, as opposed to TerminateThread. And even if one of the OS API does handle it, id be surprised if that's the case across all OS. So, for animated GIFs I think you really need your own codec? (or, if the OS API supports specifying a frame you could just use your own timer/thread?)
walbus
Addict
Addict
Posts: 929
Joined: Sat Mar 02, 2013 9:17 am

Re: ImagePlugin Module (UseWindowsImageDecoder)

Post by walbus »

What is the reason, Gif is often not supported in software !?!?

https://www.propointgraphics.com/blog/a ... right-law/
Post Reply