Fast and simple JPEG comment read/write
Posted: Wed Jan 21, 2026 9:30 am
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