Data 2 PNG (App)

Mac OSX specific forum
jamirokwai
Addict
Addict
Posts: 802
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Data 2 PNG (App)

Post by jamirokwai »

Hi there,

while investigating some ideas, converting data to images, I came across this method. This small app will copy data into PNG-images...
But only to a certain size, I think it is about 10mb. It does so by converting 4 bytes of data into one pixel.

This way, you can obscure your data, and upload the resulting images to Flickr (as they give you 1tb now...)

Happy coding :-)
Try to decode one of your PNG images (a small one!). You will probably find something hidden in there like "Smoke Marijuana".

Edited to remove the word "hide", as this does not *hide* data inside the images. Thanks, wilbert!

Version 1.0.2
- added LZMA-Compression
- made a quick test using flickr as harddrive :-)

Version 1.1.1
- increased speed a lot (using DrawingBuffer())
- some smaller tweaks
- much bigger files (than 10mb) work now (tested with 150mb)

Version 1.2.0
- added MD5
- added optional AES
- source code now available (should compile on Windows and Linux, too)
- find images here: http://pb.quadworks.de/Gluecksklee_raus.png and http://pb.quadworks.de/Gluecksklee_rein.png
- download the App for OS X from http://www.the-screaming-eye.com/Data2PNG

Code: Select all

#Title = "Data 2 PNG"
#version = "1.2.0"
; (c) 2013, The Screaming Eye (www.the-screaming-eye.com)
; Save one file inside a PNG-image, and reveal the saved data.
; Automatically pack using LZMA, if not compressible, just store.
; Add optional AES256, saves MD5 to compare stored and recovered file

EnableExplicit

Global Window_0, event, Encode, Decode, Result, Kleerein, Kleeraus, link, lzma
Global packen, cyphern, cyph, md5gad
Global md5.s = Space(32), md52.s = Space(32)

Procedure HideDataInPng(*data, size, packsize = 0)
  Protected position=0,i,v
  Protected maxx = 1, maxy = 1, imageid, imagewidth
  Protected *dest
  
  ; try to get a quadratic image
  imagewidth = Sqr(size) + 2
  
  ; we store 4 bytes in each pixel. Add 1 pixel to store data size
  If size / imagewidth > 1
    maxy = size / imagewidth + 1
    maxx = imagewidth
  Else
    maxx = size / 4 + size % 4 + 1
  EndIf
  
  ; create image of minimum needed size
  imageid = CreateImage(#PB_Any,maxx,maxy,32)
  
  ; draw and hide
  StartDrawing(ImageOutput(imageid))
  *dest = DrawingBuffer()
  DrawingMode(#PB_2DDrawing_AllChannels)
  
  ; save packedsize in another pixel
    CopyMemory(*data,*dest,size)
    Plot(maxx-2,maxy-1,packsize)
 
  ; save datasize in very last pixel
  Plot(maxx-1,maxy-1,size)  
  
  ;save the MD5
  For i = 0 To 8
    v = PeekL(@md5+i*4)
    Plot(maxx-3-i,maxy-1,v)
  Next i
  
  StopDrawing()
  
  ProcedureReturn imageid
EndProcedure

Procedure RevealDataFromPng(ImageId)
  Protected size, maxx, maxy, i, v
  Define *Data, imagewidth, originalsize, *packed, *Source, key.s
  
  StartDrawing(ImageOutput(ImageId))
  *Source = DrawingBuffer()   ; get address of drawing buffer
  DrawingMode(#PB_2DDrawing_AllChannels)
  
  maxx = ImageWidth(ImageId)
  maxy = ImageHeight(ImageId)
  
  size = Point(maxx-1,maxy-1)     ; Original Size
  originalsize = Point(maxx-2,maxy-1) ; Packed Size
  
  If size = 0
    StopDrawing()
    ProcedureReturn -1  
  EndIf
  
  If originalsize = 0
    originalsize = size
  EndIf
    
  *packed = AllocateMemory(originalsize)
  *Data = AllocateMemory(originalsize)
  CopyMemory(*source,*packed,size)
  
  ; get the MD5
  For i = 0 To 8
    v = Point(maxx-3-i,maxy-1)
    PokeL(@md5+i*4,v)
  Next i
  
  StopDrawing()
  
  Debug size ; 314
  Debug originalsize ; 445
  
  ; De-Cypher data
  If GetGadgetState(cyphern) = 1
    key = GetGadgetText(cyph)
    If AESDecoder(*packed, *data, size, @Key, 128, ?InitializationVector)
      CopyMemory(*data,*packed,size)
    EndIf
  EndIf   
      
  If OriginalSize <> size
    size = UncompressMemory(*packed,size,*data,originalsize,#PB_Packer_LZMA)
    lzma = 1
  Else
    CopyMemory(*packed,*data,size)
    lzma = 0
  EndIf
  
  md52 = MD5Fingerprint(*data,originalsize) 
    
  FreeMemory(*packed)
  ProcedureReturn *data
EndProcedure

Procedure OpenWindow_0()
  Window_0 = OpenWindow(#PB_Any, 100, 100, 420, 210, #Title + " " + #Version, #PB_Window_SystemMenu) 
  Encode = ButtonImageGadget(#PB_Any, 10, 10, 120, 120, ImageID(KleeRein),#PB_Image_Raised)
  GadgetToolTip(Encode, "Drop a file to convert it to PNG.")
  EnableGadgetDrop(Encode,#PB_Drop_Files,#PB_Drag_Copy|#PB_Drag_Copy|#PB_Drag_Move|#PB_Drag_Link)
  
  Decode = ButtonImageGadget(#PB_Any, 290, 10, 120, 120, ImageID(KleeRaus),#PB_Image_Raised)
  GadgetToolTip(Decode, "Drop a PNG to recover the saved file.")
  EnableGadgetDrop(Decode,#PB_Drop_Files,#PB_Drag_Copy|#PB_Drag_Copy|#PB_Drag_Move|#PB_Drag_Link)
  
  TextGadget(#PB_Any, 140, 10, 150, 120, "Copy to and get data from PNGs. Or look for secret messages hidden " +
                                         "inside your images..." + #CRLF$ + "(c) 2013 by http://the-screaming-eye.com")
  
  link = HyperLinkGadget(#PB_Any, 8, 190, 420, 24, "'Glücksklee' by Kolossos, 2005, deriative work by the-screaming-eye.com.",RGB($1f,$1f,$9f),#PB_HyperLink_Underline)
  GadgetToolTip(link,"Click to visit http://www.the-screaming-eye.com")
  
  packen = CheckBoxGadget(#PB_Any, 10, 140, 130, 20, "Pack (with LZMA)")
  cyphern = CheckBoxGadget(#PB_Any, 140, 140, 130, 20, "UnScramble (AES)")
  cyph = StringGadget(#PB_Any, 280, 140, 130, 20, "")
  md5gad = StringGadget(#PB_Any, 10, 164, 400, 20, "MD5: ")
  
  GadgetToolTip(packen, "Check, if you like to have your file packed before putting it into the PNG. Only applies, if compressible." + #CRLF$ + "Decompression is automatically used, if needed.")
  GadgetToolTip(cyphern, "Check, if you like to have your file scrambled with a 16-digit passphrase.")
  GadgetToolTip(cyph, "Enter your passphrase here. Remember it, you cannot unscramble the content without it!")
  GadgetToolTip(md5gad, "MD5 of latest operation. Please copy, if you need it.")
  
  SetGadgetState(packen,1)
  DisableGadget(cyph,1)
  
  Result = TextGadget(#PB_Any, 140, 114, 130, 25, "")
  AddKeyboardShortcut(Window_0, #PB_Shortcut_Command | #PB_Shortcut_Q, #PB_Event_CloseWindow)
EndProcedure

Procedure Encode_Data(filename$,output$)
  Protected *myData, fileid, imageid, size, packsize = 0, *packed, key.s
  
  fileid = OpenFile(#PB_Any,filename$)
  size = FileSize(filename$)

  If size > 0
    *myData = AllocateMemory(size)
    *packed = AllocateMemory(size)
    If *myData
      ReadData(fileid,*myData,size)
      CloseFile(fileID)
      
      md5 = MD5Fingerprint(*myData,size)
      ; Pack data
      If GetGadgetState(packen) = 1
        packsize = CompressMemory(*myData,size,*packed,size, #PB_Packer_LZMA)
      EndIf
      
      ; Cypher data
      If GetGadgetState(cyphern) = 1
        key = GetGadgetText(cyph)
        If packsize <> 0
        
          If AESEncoder(*packed, *myData, packsize, @Key, 128, ?InitializationVector)
            CopyMemory(*myData,*packed,packsize)
            Debug "ja-pack"
          EndIf
        Else
          If AESEncoder(*myData, *packed, size, @Key, 128, ?InitializationVector)
            CopyMemory(*packed,*myData,size)
            Debug "ja-nicht-pack"
          EndIf
        EndIf
      EndIf
      
      If packsize <> 0
        imageid = HideDataInPng(*packed,packsize,size)
        lzma = 1
      Else
        imageid = HideDataInPng(*myData,size)
        lzma = 0
      EndIf
      
      If IsImage(imageid)
        SaveImage(imageid,output$,#PB_ImagePlugin_PNG,0,32)
        FreeMemory(*myData)
        FreeMemory(*packed)
        ProcedureReturn imageid
      Else
        ProcedureReturn -1
      EndIf
    Else
      ProcedureReturn -1
    EndIf
  Else
    ProcedureReturn -1
  EndIf
EndProcedure

Procedure Decode_Data(filename$,output$)
  Protected *myData, fileid, imageid, size
  
  imageid = LoadImage(#PB_Any,filename$)
  If IsImage(imageid)
    *myData = RevealDataFromPng(imageid)
    If *myData = -1
      ProcedureReturn -1
    EndIf
    
    size = MemorySize(*myData)
    Debug md5
    Debug md52
    
    If size <> 0
      fileid = CreateFile(#PB_Any,output$)
      WriteData(fileid,*mydata,size)
      CloseFile(fileid)
      FreeMemory(*myData)
      ProcedureReturn imageid
    Else 
      ProcedureReturn -1
    EndIf
  Else
    ProcedureReturn -1
  EndIf
EndProcedure

Procedure Window_0_Events(event)
  Protected File$, Filename$, imageid, temp, file2$
  
  Select event
    Case #PB_Event_CloseWindow,#PB_Event_Menu
      End
    
    Case #PB_Event_GadgetDrop, #PB_Event_Gadget ;{ Drop
      
    If event = #PB_Event_Gadget
      Select EventGadget()
        Case packen, md5gad
          ProcedureReturn
        Case cyph ; Passphrase has to be 16 chars
          If Len(GetGadgetText(cyph))>16
            SetGadgetText(cyph,Left(GetGadgetText(cyph),16))
          EndIf
          ProcedureReturn
        Case cyphern
          If GetGadgetState(cyphern) = 1
            DisableGadget(cyph,0)
          Else
            DisableGadget(cyph,1)
          EndIf
          ProcedureReturn
        Case link
          RunProgram("open","http://www.the-screaming-eye.com","")
          ProcedureReturn
      EndSelect
    EndIf
    ;}
    ;{ Umwandeln
      temp = Len(GetGadgetText(cyph))
      If Temp <> 16 And Temp <> 0
        MessageRequester("Information","Please enter a 16 digit passphrase.")
        SetGadgetText(Result,"Passphrase too short")
        ProcedureReturn
      EndIf    
    
      SetGadgetState(decode,ImageID(KleeRaus))
      SetGadgetState(encode,ImageID(KleeRein))
      SetGadgetText(Result,"Starting")
      If event = #PB_Event_GadgetDrop
        File$ = Trim(EventDropFiles(),Chr(10))
      Else
        File$ = OpenFileRequester("Select source file","input","",0)
        If file$ = ""
          SetGadgetText(Result,"Stopped. No Source!")
          ProcedureReturn
        EndIf
      EndIf
      If FindString(file$,Chr(10)) <> 0
         MessageRequester("Information","Please drop only single files here.")
       Else
         
         If FileSize(file$) > 1024*1024*10 ; mehr als 10 mb
           SetGadgetText(Result,"Filesize: " + StrF(FileSize(file$)/1024/1024,2) + "mb")
         EndIf
         
         Select EventGadget()
           Case encode
             filename$ = SaveFileRequester("Select destination file",file$ + ".png","",0)
             If filename$ = ""
               SetGadgetText(Result,"No destination!")
               ProcedureReturn
             EndIf
             SetGadgetText(md5gad,"MD5: ")
             imageid = Encode_Data(file$,filename$)
             If imageid = -1
               If IsImage(imageid)
                 FreeImage(imageid)
               EndIf
               SetGadgetText(Result,"Conversion failed!")
               ProcedureReturn
             Else
               If IsImage(imageid)
                 ResizeImage(imageid,120,120,#PB_Image_Raw)
                 SetGadgetState(encode,ImageID(imageid))
                 FreeImage(imageid)
               EndIf
               If lzma = 1
                 SetGadgetText(Result,"OK. Packed!")
               Else
                 SetGadgetText(Result,"OK.")
               EndIf               
             EndIf
             SetGadgetText(md5gad,"MD5: " + md5)
             SetClipboardText(md5)
             
           Case decode
             file2$ = ReplaceString(file$,".png","")
             filename$ = SaveFileRequester("Select destination file",file2$,"",0)
             If filename$ = ""
               SetGadgetText(Result,"No destination!")
               ProcedureReturn
             EndIf
             SetGadgetText(md5gad,"MD5: ")
             imageid = Decode_Data(file$,filename$)
             If imageid = -1
               MessageRequester("Sorry","Conversion failed.")
               SetGadgetText(Result,"Conversion failed!")
               ProcedureReturn
             Else
               If lzma = 1
                 SetGadgetText(Result,"OK. Unpacked!")
               Else
                 SetGadgetText(Result,"OK.")
               EndIf               
             EndIf
             SetGadgetText(md5gad,"MD5: " + md52)
             SetClipboardText(md52)
             If md5 <> md52
               MessageRequester("Information","The MD5 of the saved and recovered files are different. Either an error happened, or the file was compromised.")
               SetGadgetText(Result,"MD5 not identical")
             EndIf
               
           EndSelect
         EndIf
         ;}
           
       EndSelect
  ProcedureReturn #True
EndProcedure

; Test

UsePNGImageDecoder()
UsePNGImageEncoder()
UseLZMAPacker()
Kleerein = CatchImage(#PB_Any,?kleerein,?kleeraus-?kleerein)
Kleeraus = CatchImage(#PB_Any,?kleeraus,?kleeende-?kleeraus)

OpenWindow_0()

Repeat
  Event = WaitWindowEvent()
  Window_0_Events(Event)
ForEver

DataSection
  kleerein: IncludeBinary "Gluecksklee_rein.png"
  kleeraus: IncludeBinary "Gluecksklee_raus.png"
  kleeende:
      
  InitializationVector: Data.b $3d, $af, $ba, $42, $9d, $9e, $b4, $30, $b4, $22, $da, $80, $2c, $9f, $ac, $41
EndDataSection
Last edited by jamirokwai on Wed Jun 05, 2013 8:13 am, edited 8 times in total.
Regards,
JamiroKwai
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3944
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Hide 2 PNG (App)

Post by wilbert »

It's obvious something is the matter with an image if you put 4 bytes into one pixel.
There will be no room left for the image itself. It makes more sense to put like 4 bits or so into one pixel.
Windows (x64)
Raspberry Pi OS (Arm64)
jamirokwai
Addict
Addict
Posts: 802
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: Hide 2 PNG (App)

Post by jamirokwai »

wilbert wrote:It's obvious something is the matter with an image if you put 4 bytes into one pixel.
There will be no room left for the image itself. It makes more sense to put like 4 bits or so into one pixel.
Yes, you're right, but that was not my idea :-)
I just wanted to put data into an image-file, not hide it.

My fault, the description and texts are wrong! Changed my original post to reflect this, thanks for commenting, wilbert!
Regards,
JamiroKwai
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3944
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Data 2 PNG (App)

Post by wilbert »

Doesn't Flickr use any checks to prevent this ?

I'm wondering how you approached it.
The png format isn't very complicated but I'm curious how you retrieve the original information.
Do you store the file size as well ?
Windows (x64)
Raspberry Pi OS (Arm64)
jamirokwai
Addict
Addict
Posts: 802
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: Data 2 PNG (App)

Post by jamirokwai »

wilbert wrote:Doesn't Flickr use any checks to prevent this ?

I'm wondering how you approached it.
The png format isn't very complicated but I'm curious how you retrieve the original information.
Do you store the file size as well ?
Don't know, if Flickr checks for this information. I didn't try uploading, yet.

Yes, just save a 32 Bit Long into RGBA-Values, and read it back.Works well, actually, which I didn't expect :-)
The Length is saved in the very last pixel, so you have to add one line or one column.
Regards,
JamiroKwai
jamirokwai
Addict
Addict
Posts: 802
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: Data 2 PNG (App)

Post by jamirokwai »

New version 1.0.2

I made a quick test with some PB-sources, uploaded them to Flickr, downloaded them. Then, I was able to recover the complete sources...
Please download from first post.

I will set up a new page on my homepage to reflect the project later this week... Edit: Site is set up, see first post!
Regards,
JamiroKwai
jamirokwai
Addict
Addict
Posts: 802
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: Data 2 PNG (App)

Post by jamirokwai »

Version 1.1.1
- increased speed a lot (using DrawingBuffer())
- some smaller tweaks
- much bigger files (than 10mb) work now
Regards,
JamiroKwai
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3944
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Data 2 PNG (App)

Post by wilbert »

If you wish, you can do this without the image routines by directly creating a png file yourself.
It will probably be faster since you can simply copy memory.
Windows (x64)
Raspberry Pi OS (Arm64)
jamirokwai
Addict
Addict
Posts: 802
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: Data 2 PNG (App)

Post by jamirokwai »

wilbert wrote:If you wish, you can do this without the image routines by directly creating a png file yourself.
It will probably be faster since you can simply copy memory.
How would you do this? :-)
I am currently using the SaveImageEX, you coded (http://www.forums.purebasic.com/english ... 5&start=15)
The LoadImageEX does not work 100% correctly, as there is something missing in the loaded image, the sizes in the last 2 pixels. I am investigating this...

I improved speed a lot by copying the buffer data into the image-buffer... Great (!) enhancement.
Regards,
JamiroKwai
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3944
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Data 2 PNG (App)

Post by wilbert »

One problem with using 32 bit RGBA data, is that Cocoa premultiplies alpha.
As a result of this, information might be gone. It would probably be safer to use 24 bit RGB or 8 bit grayscale instead.

Here's an example of creating a PNG manually.
What you do is simply create the IHDR, IDAT and IEND chunks yourself.
PNG requires IDAT (the actual data) to be ZLIB compressed so there's probably no much to gain from using LZMA before that.

Code: Select all

CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Linux
    #ZLIB_Filename = #PB_Compiler_Home + "purelibraries/linux/libraries/zlib.a"
  CompilerCase #PB_OS_MacOS
    #ZLIB_Filename = "/usr/lib/libz.dylib"
  CompilerCase #PB_OS_Windows
    #ZLIB_Filename = "zlib.lib"
CompilerEndSelect

ImportC #ZLIB_Filename
  compress2(*dest, *destLen, *source, sourceLen, level)
  compressBound(sourceLen)
  crc32(crc, *buf, len)
  uncompress(*dest, *destLen, *source, sourceLen)
EndImport

Procedure PeekL_Big(*MemoryBuffer); Peek long, big endian
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
    !mov edx, [p.p_MemoryBuffer]
    !mov eax, [edx]
  CompilerElse
    !mov rdx, [p.p_MemoryBuffer]
    !mov eax, [rdx]
  CompilerEndIf
  !bswap eax
  ProcedureReturn  
EndProcedure

Procedure PokeL_Big(*MemoryBuffer, Number); Poke long, big endian
  !mov eax, [p.v_Number]
  !bswap eax
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
    !mov edx, [p.p_MemoryBuffer]
    !mov [edx], eax
  CompilerElse
    !mov rdx, [p.p_MemoryBuffer]
    !mov [rdx], eax
  CompilerEndIf
EndProcedure

Procedure Data2PNG(*Memory, Size, compressionLevel = 6)
  Protected width, height, usize, csize
  Protected *uncompressed, *compressed, *png, *p, *p0
  
  width = Sqr(Size)
  height = (Size + 4 + width - 1) / width; add 4 extra bytes for storing size
  
  usize = (width + 1) * height; calculate required uncompressed size
  *uncompressed = AllocateMemory(usize)
  If *uncompressed
    
    ; store size
    PokeL(*uncompressed + usize - 4, Size)
    
    ; copy memory data
    *p = *uncompressed
    While Size >= width
      CopyMemory(*Memory, *p + 1, width)
      *Memory + width
      *p + width + 1
      Size - width
    Wend
    CopyMemory(*Memory, *p + 1, Size)
    
    ; compress data  
    csize = compressBound(usize)
    *compressed = AllocateMemory(csize)
    If *compressed
      If compress2(*compressed, @csize, *uncompressed, usize, compressionLevel) = 0
        FreeMemory(*uncompressed) : *uncompressed = 0
        *png = AllocateMemory(csize + 57)
        If *png
          *p0 = *png
          ; PNG signature
          PokeQ(*png, $0A1A0A0D474E5089) : *png + 8
          ; IHDR chunk (8 bit grayscale image)
          PokeL_Big(*png, 13) : *png + 4 : *p = *png
          PokeS(*png, "IHDR", 4, #PB_Ascii) : *png + 4
          PokeL_Big(*png, width) : *png + 4
          PokeL_Big(*png, height) : *png + 4
          PokeQ(*png, 8) : *png + 5
          PokeL_Big(*png, crc32(0, *p, *png - *p)) : *png + 4
          ; IDAT chunk
          PokeL_Big(*png, csize) : *png + 4 : *p = *png
          PokeS(*png, "IDAT", 4, #PB_Ascii) : *png + 4
          CopyMemory(*compressed, *png, csize) : *png + csize
          PokeL_Big(*png, crc32(0, *p, *png - *p)) : *png + 4    
          ; IEND chunk
          PokeL(*png, 0) : *png + 4 : *p = *png
          PokeS(*png, "IEND", 4, #PB_Ascii) : *png + 4
          PokeL_Big(*png, crc32(0, *p, *png - *p)) : *png + 4   
          *png = *p0
        EndIf
      EndIf
      FreeMemory(*compressed)
    EndIf
    If *uncompressed : FreeMemory(*uncompressed) : EndIf
  EndIf
  
  ProcedureReturn *png
EndProcedure

Procedure LoadPNG2Data(FileName.s)
  Protected result, *png, *uncompressed, offset, y, width, height
  Protected size, file = ReadFile(#PB_Any, FileName)
  If file
    size = Lof(file)
    *png = AllocateMemory(size)
    If *png
      ReadData(file, *png, size)
      
      ; find IDAT chunk and uncompress
      If PeekQ(*png) = $0A1A0A0D474E5089 And PeekS(*png + 12, 4, #PB_Ascii) = "IHDR"
        width = PeekL_Big(*png + 16)
        height = PeekL_Big(*png + 20)
        offset = 37
        While offset <= size - 20
          If PeekL(*png + offset) = $54414449
            size = (width + 1) * height
            *uncompressed = AllocateMemory(size)
            If *uncompressed
              If uncompress(*uncompressed, @size, *png + offset + 4, PeekL_Big(*png + offset - 4)) = 0
                If size = (width + 1) * height
                  size = PeekL(*uncompressed + size - 4)  
                Else
                  size = 0
                EndIf
              Else
                size = 0  
              EndIf
              If size = 0
                FreeMemory(*uncompressed) : *uncompressed = 0
              EndIf
            EndIf
            Break
          EndIf
          offset + 1
        Wend
      EndIf
      FreeMemory(*png)
    EndIf
    
    ; extract original data
    If *uncompressed
      While y < height
        MoveMemory(*uncompressed + (width + 1) * y + 1, *uncompressed + width * y, width)
        y + 1
      Wend
      *uncompressed = ReAllocateMemory(*uncompressed, size)
    EndIf
    
  EndIf
  ProcedureReturn *uncompressed
EndProcedure

Procedure SaveData2PNG(*Memory, Size, FileName.s, compressionLevel = 6)
  Protected result, file, *png = Data2PNG(*memory, Size, compressionLevel)
  If *png
    file = CreateFile(#PB_Any, FileName)
    If file
      Size = MemorySize(*png)
      If WriteData(file, *png, Size) = Size
        result = 1
      EndIf
      CloseFile(file)
    EndIf
    FreeMemory(*png)
  EndIf
  ProcedureReturn result  
EndProcedure



; test

Define *mem = AllocateMemory(1638400)
RandomData(*mem, 1638400)
SaveData2PNG(*mem, 1638400, "Data2PNG.png")

Define *mem2 = LoadPNG2Data("Data2PNG.png")

If CompareMemory(*mem, *mem2, 1638400)
  MessageRequester("", "Equal")
EndIf
To reverse, you can open the image. Since it's grayscale, each pixel contains one byte and the last four pixels contain the size of the original file.
Another way is to search for the IDAT chunk, decompress it and copy the data out of it. The decompressed data is simply the original data preceded with a zero byte before for each line of pixels (this is a filter byte the PNG specification uses). Using the second way, you don't need any image routines.
Using the PureBasic CompressMemory procedure with #PB_Packer_Zip should also work in most cases. The reason why I chose to import compress2 is because PNG always requires the IDAT chunk to be compressed and I believe PureBasic won't compress if the result isn't smaller as the original data.
Last edited by wilbert on Wed Jun 05, 2013 7:28 am, edited 2 times in total.
Windows (x64)
Raspberry Pi OS (Arm64)
jamirokwai
Addict
Addict
Posts: 802
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: Data 2 PNG (App)

Post by jamirokwai »

wilbert wrote:One problem with using 32 bit RGBA data, is that Cocoa premultiplies alpha.
As a result of this, information might be gone. It would probably be safer to use 24 bit RGB or 8 bit grayscale instead.
Seems, you're right. The SaveImageEX breaks the data, I add to the end. Thanks for the example, I will have a look.
wilbert wrote:PNG requires IDAT (the actual data) to be ZLIB compressed so there's probably no much to gain from using LZMA before that.
Interestingly, LZMA-Compression does a good work on sequences like aabbaa. With LZMA, the resulting PNG of 160k was 275 Bytes (!), the resulting PNG without LZMA was about 5k, which is still a great compression-ratio. It will heavily depend on the source, e.g. MP3 and JPEG won't be compacted much further.
Regards,
JamiroKwai
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3944
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Data 2 PNG (App)

Post by wilbert »

I updated my code example above to also include a load procedure.
If Flickr doesn't mess with the original file, it should work.
It also seems to work fine on Windows now.
Windows (x64)
Raspberry Pi OS (Arm64)
jamirokwai
Addict
Addict
Posts: 802
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: Data 2 PNG (App)

Post by jamirokwai »

wilbert wrote:I updated my code example above to also include a load procedure.
If Flickr doesn't mess with the original file, it should work.
It also seems to work fine on Windows now.
Nice :-)

I added version 1.2.0 including MD5 and working AES-encryption.
See the complete source in first post.

There are some ideas left to follow, but time flies.
Regards,
JamiroKwai
Post Reply