Fast and simple JPEG comment read/write

Share your advanced PureBasic knowledge/code with the community.
moricode
Enthusiast
Enthusiast
Posts: 168
Joined: Thu May 25, 2023 3:55 am

Fast and simple JPEG comment read/write

Post by moricode »

a peace of code to save comment in .jpg file and read it back , the use of this function is beyond the imagination , has fun ...



Code: Select all

; JPEG Comment Reader/Writer
; Reads and writes COM (Comment) segments in JPEG files

; JPEG Marker Constants (in Little Endian format)= (Ms PC)
#JPEG_SOI = $FFD8  ; Start of Image
#JPEG_EOI = $FFD9  ; End of Image
#JPEG_COM = $FFFE  ; Comment marker
#JPEG_APP0 = $FFE0 ; JFIF marker

Procedure.l ReadWordBE(*Memory)
   ; Convert to Littel Endian format
    ProcedureReturn ((PeekB(*Memory) << 8) | PeekB(*Memory + 1) ) &$FFFF
EndProcedure

Procedure WriteWordBE(*Memory, Value.w)
  ; Write in big-endian format , high value put in low address, low value put in high address
  PokeB(*Memory, (Value >> 8) & $FF)
  PokeB(*Memory + 1, Value & $FF)
EndProcedure

Procedure.s ReadJPEGComment(Filename.s)
  PROTECTED File, FileSize.l, *Buffer, *Ptr, Marker.l, SegmentLength.l
  PROTECTED Comment.s = ""
  
  File = ReadFile(#PB_Any, Filename)
  IF File
    FileSize = Lof(File)
    *Buffer = AllocateMemory(FileSize)
    
    IF *Buffer
      ReadData(File, *Buffer, FileSize)
      CloseFile(File)
      
      *Ptr = *Buffer

      IF ReadWordBE(*Ptr) = #JPEG_SOI
         *Ptr + 2
        ; Scan through segments
        While *Ptr < *Buffer + FileSize - 2
          Marker = ReadWordBE(*Ptr)
          *Ptr + 2
          
          IF (Marker & $FF00) <> $FF00
            Break ; Invalid marker
          ENDIF
          
          ; Check if this is a COM segment
          IF Marker = #JPEG_COM
            SegmentLength = ReadWordBE(*Ptr)
            *Ptr + 2

            IF SegmentLength > 2
              Comment = PeekS(*Ptr, SegmentLength - 2, #PB_UTF8)
            ENDIF
            Break
          ELSEIF Marker = #JPEG_EOI Or Marker = $FFD0 Or Marker = $FFD1 Or Marker = $FFD2 Or Marker = $FFD3 Or Marker = $FFD4 Or Marker = $FFD5 Or Marker = $FFD6 Or Marker = $FFD7
            Continue
          ELSEIF Marker >= $FF01 And Marker <= $FFBF
            ; Skip this segment
            SegmentLength = ReadWordBE(*Ptr)
            *Ptr + SegmentLength
          ELSE
            Break
          ENDIF
        Wend
      ENDIF
      
      FreeMemory(*Buffer)
    ELSE
      CloseFile(File)
    ENDIF
  ENDIF
  ProcedureReturn Comment
EndProcedure

Procedure WriteJPEGComment(Filename.s, Comment.s, OutputFilename.s = "")
  PROTECTED File, FileSize.l, *Buffer, *NewBuffer, *Ptr, *WritePtr
  PROTECTED Marker, SegmentLength, CommentBytes.l, NewSize.l
  PROTECTED Result = #False, HasExistingComment = #False
  PROTECTED OutFile.s, *CommentStart, CommentLength.w
  PROTECTED RemainingSize.l
  
  IF OutputFilename = ""
    OutFile = Filename
  ELSE
    OutFile = OutputFilename
  ENDIF
  
  File = ReadFile(#PB_Any, Filename)
  IF Not File
    ProcedureReturn #False
  ENDIF
  
  FileSize = Lof(File)
  *Buffer = AllocateMemory(FileSize)
  
  IF Not *Buffer
    CloseFile(File)
    ProcedureReturn #False
  ENDIF
  
  ReadData(File, *Buffer, FileSize)
  CloseFile(File)
  
  IF ReadWordBE(*Buffer) <> #JPEG_SOI
     FreeMemory(*Buffer)
    ProcedureReturn #False
  ENDIF  
  
  ; Scan for existing COM segment
  *Ptr = *Buffer + 2
  While *Ptr < *Buffer + FileSize - 2
    Marker = ReadWordBE(*Ptr)
    
    IF (Marker & $FF00) <> $FF00
      Break
    ENDIF
    
    IF Marker = #JPEG_COM
      HasExistingComment = #True
      *CommentStart = *Ptr
      CommentLength = ReadWordBE(*Ptr + 2)
      Break
    ELSEIF Marker = #JPEG_EOI Or (Marker >= $FFD0 And Marker <= $FFD7)
      *Ptr + 2
      Continue
    ELSEIF Marker >= $FFC0
      Break ; Reached image data
    ELSE
      SegmentLength = ReadWordBE(*Ptr + 2)
      *Ptr + 2 + SegmentLength
    ENDIF
  Wend
  
  ; Calculate new comment size
  CommentBytes = StringByteLength(Comment, #PB_UTF8)
  
  IF HasExistingComment
    NewSize = FileSize - CommentLength + 4 + CommentBytes
  ELSE
    NewSize = FileSize + 4 + CommentBytes
  ENDIF
  
  *NewBuffer = AllocateMemory(NewSize)
  IF Not *NewBuffer
    FreeMemory(*Buffer)
    ProcedureReturn #False
  ENDIF
  
  *WritePtr = *NewBuffer
  
  IF HasExistingComment
    ; Copy up to existing comment
    CopyMemory(*Buffer, *WritePtr, *CommentStart - *Buffer)
    *WritePtr + (*CommentStart - *Buffer)
    
    ; Write new COM segment
    WriteWordBE(*WritePtr, #JPEG_COM)
    *WritePtr + 2
    WriteWordBE(*WritePtr, CommentBytes + 2)
    *WritePtr + 2
    PokeS(*WritePtr, Comment, -1, #PB_UTF8 | #PB_String_NoZero)
    *WritePtr + CommentBytes
    
    ; Copy rest after old comment
    *Ptr = *CommentStart + 2 + CommentLength
    RemainingSize = FileSize - (*Ptr - *Buffer)
    CopyMemory(*Ptr, *WritePtr, RemainingSize)
  ELSE
    ; Copy SOI
    WriteWordBE(*WritePtr, #JPEG_SOI)
    *WritePtr + 2
    
    ; Write COM segment
    WriteWordBE(*WritePtr, #JPEG_COM)
    *WritePtr + 2
    WriteWordBE(*WritePtr, CommentBytes + 2)
    *WritePtr + 2
    PokeS(*WritePtr, Comment, -1, #PB_UTF8 | #PB_String_NoZero)
    *WritePtr + CommentBytes
    
    ; Copy rest of file
    CopyMemory(*Buffer + 2, *WritePtr, FileSize - 2)
  ENDIF
  
  File = CreateFile(#PB_Any, OutFile)
  IF File
    WriteData(File, *NewBuffer, NewSize)
    CloseFile(File)
    Result = #True
  ENDIF
  
  FreeMemory(*NewBuffer)
  FreeMemory(*Buffer)
  
  ProcedureReturn Result
EndProcedure

Enumeration
  #Window
  #FileGadget
  #BrowseButton
  #CommentEditor
  #ReadButton
  #WriteButton
  #StatusBar
EndEnumeration

IF OpenWindow(#Window, 0, 0, 600, 400, "JPEG Comment Editor", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  TextGadget(#PB_Any, 10, 10, 100, 25, "JPEG File:")
  StringGadget(#FileGadget, 110, 10, 380, 25, "")
  ButtonGadget(#BrowseButton, 500, 10, 80, 25, "Browse")
  
  TextGadget(#PB_Any, 10, 50, 580, 25, "Comment:")
  EditorGadget(#CommentEditor, 10, 75, 580, 250, #PB_Editor_WordWrap)
  
  ButtonGadget(#ReadButton, 10, 335, 280, 30, "Read Comment from JPEG")
  ButtonGadget(#WriteButton, 310, 335, 280, 30, "Write Comment to JPEG")
  
  CreateStatusBar(#StatusBar, WindowID(#Window))
  AddStatusBarField(#PB_Ignore)
  StatusBarText(#StatusBar, 0, "Ready")
  
  Repeat
    Event = WaitWindowEvent()
    
    SELECT Event
      CASE #PB_Event_CloseWindow
        Break
        
      CASE #PB_Event_Gadget
        SELECT EventGadget()
          CASE #BrowseButton
            Pattern$ = "JPEG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg|All Files (*.*)|*.*"
            FileName$ = OpenFileRequester("Select JPEG File", "", Pattern$, 0)
            IF FileName$
              SetGadgetText(#FileGadget, FileName$)
              StatusBarText(#StatusBar, 0, "File selected: " + GetFilePart(FileName$))
            ENDIF
            
          CASE #ReadButton
            FileName$ = GetGadgetText(#FileGadget)
            IF FileName$ And FileSize(FileName$) > 0
              Comment$ = ReadJPEGComment(FileName$)
              SetGadgetText(#CommentEditor, Comment$)
              IF Comment$ = ""
                StatusBarText(#StatusBar, 0, "No comment found in JPEG file")
              ELSE
                StatusBarText(#StatusBar, 0, "Comment loaded successfully (" + Str(Len(Comment$)) + " characters)")
              ENDIF
            ELSE
              MessageRequester("Error", "Please select a valid JPEG file first")
            ENDIF
            
          CASE #WriteButton
            FileName$ = GetGadgetText(#FileGadget)
            IF FileName$ And FileSize(FileName$) > 0
              Comment$ = GetGadgetText(#CommentEditor)
              
              Pattern$ = "JPEG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg"
              OutFile$ = SaveFileRequester("Save JPEG with Comment", GetPathPart(FileName$) + "commented_" + GetFilePart(FileName$), Pattern$, 0)
              
              IF OutFile$
                IF WriteJPEGComment(FileName$, Comment$, OutFile$)
                  StatusBarText(#StatusBar, 0, "Comment written successfully to: " + GetFilePart(OutFile$))
                  MessageRequester("Success", "JPEG comment written successfully!")
                ELSE
                  StatusBarText(#StatusBar, 0, "Failed to write comment")
                  MessageRequester("Error", "Failed to write comment to JPEG file")
                ENDIF
              ENDIF
            ELSE
              MessageRequester("Error", "Please select a valid JPEG file first")
            ENDIF
        ENDSELECT
    ENDSELECT
  ForEver
  
ENDIF

User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5623
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Fast and simple JPEG comment read/write

Post by Kwai chang caine »

Works nice here, can be usefull
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination

PureBasic French Forum
User avatar
idle
Always Here
Always Here
Posts: 6187
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Fast and simple JPEG comment read/write

Post by idle »

that's really useful thanks
Post Reply