Skip to content

Write png comments / EXIF-like metadata from R to the png files #75

@vertesy

Description

@vertesy

Introduction and goals.

  1. Write png comments / EXIF-like metadata from R to the png files.
  2. Currently, it is implemented as a sidecar: filename.metadata.txt, but that is clunky

Current solution: 2

  1. Write metadata into the PNG (portable) using exiftool
    • On Linux, PNG metadata is usually stored as tEXt/iTXt chunks (not classic JPEG EXIF). exiftool
      can write these chunks.
  2. Sidecar metadata file (dead simple, works everywhere).
    • This is often the most practical on HPC: no system dependencies.
  3. Write PNG comment chunk inside the PNG filewith MagicK

Complications with metadata inside and outside the PNG file.

Finder won’t show MagicK comments:

  1. Finder “Comments” = extended attribute (com.apple.metadata:kMDItemFinderComment)
  2. PNG comment chunk = inside the PNG file
  • They are unrelated systems.
  • Writing Finder “Comments” is difficult and system related.

Solution: Magick + Commandline Helpers

REPREX

library(magick)

png_path <- "test.png"
meta <- "gsgExpressMeta proj=ABC | subset=BLA | commit=deadbeef | date=2026-02-02"

img <- image_read(png_path)
img <- image_comment(img, meta)              # set comment :contentReference[oaicite:1]{index=1}
image_write(img, png_path, format = "png")   # write

# read back
img2 <- image_read(png_path)
image_comment(img2)                          # get comment :contentReference[oaicite:2]{index=2}


library(ggplot2)
library(magick)

p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()

ggExpress::qqSave(p, fname = "test2")
img <- image_read("test2.png")
image_write(img, "test2.png", format = "png", comment = meta)


image_comment(image_read("test2.png"))

2 Helpers

pngtext() {
python3 - "$1" <<'PY'
import sys,struct,zlib
f=open(sys.argv[1],'rb'); f.read(8)
while True:
  h=f.read(8)
  if len(h)<8: break
  ln,ct=struct.unpack(">I4s",h); d=f.read(ln); f.read(4); ct=ct.decode()
  if ct=="tEXt":
    k,v=d.split(b"\0",1); print(k.decode('latin1')+": "+v.decode('latin1','replace'))
  elif ct=="zTXt":
    k,r=d.split(b"\0",1); print(k.decode('latin1')+": "+zlib.decompress(r[1:]).decode('latin1','replace'))
  elif ct=="iTXt":
    p=d.split(b"\0",4); t=p[4]; 
    if p[1][:1]==b"\1": t=zlib.decompress(t)
    print(p[0].decode('utf-8','replace')+": "+t.decode('utf-8','replace'))
  if ct=="IEND": break
PY
}
pngmeta() {
  pngtext "$1" | awk -F': ' '
    $1=="Comment" && $2 ~ /^ggExpressMeta:/ {
      sub(/^ggExpressMeta:[ ]*/, "", $2); print $2; exit
    }'
}
pngtext  figure.png
pngmeta  figure.png
ggExpressMeta: key1=... | key2=... | ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions