gcbenison
gcbenison

Reputation: 11963

Insert a text chunk into a png image

I'm looking for a simple command-line tool (on Linux) to insert a text chunk (e.g. copyright) into a png file, resulting in a new png file:

> png-insert-text-chunk "here's my text chunk" < in.png > out.png

Note: by "insert a text chunk", I do not mean "draw some text on the image". I mean: insert the text into the png file as a chunk, in the technical sense. This can be used, for example, to insert a copyright message that isn't displayed on the actual image.

Upvotes: 26

Views: 19121

Answers (9)

user2845840
user2845840

Reputation: 396

gioele already showed how to do it with ImageMagick, but I had more specific requirements that were not fulfilled by that answer. Here is what I did to definitely get an uncompressed tEXt chunk with the keyword of my choice instead of a compressed zTXt chunk:

convert input.png -set 'my_chosen_keyword' "$(cat content.txt)" -compress none output.png

Using "$(cat content.txt)" makes it easier to get text with line breaks and other ugly stuff into the file.

NB: Despite the -compress none, the PNG image data will still be as compressed as usual.

Upvotes: 1

Carl Cheung
Carl Cheung

Reputation: 558

I extracted the core algorithm of reading and writing png-text-chunk from pypng. This is the simplest example. And it's 10x faster than original pypng implementation

import struct
import zlib
from typing import Union


def write_text_chunk(img_bytes: bytes, data: Union[str, bytes]) -> bytes:
    assert img_bytes[:8] == b'\x89PNG\r\n\x1a\n'
    if isinstance(data, str):
        data = data.encode()
    start_idx = end_idx = img_bytes.find(b'tEXt') - 4
    if b'tEXt' in img_bytes:
        existed_data_len = struct.unpack("!I", img_bytes[start_idx: start_idx + 4])[0]
        end_idx += 12 + existed_data_len
    out_arr = img_bytes[:start_idx] + \
              struct.pack("!I", len(data)) + \
              b'tEXt' + data + \
              struct.pack("!I", zlib.crc32(data, zlib.crc32(b'tEXt')) & 2 ** 32 - 1) + \
              img_bytes[end_idx:]
    return out_arr


def read_text_chunk(img_bytes: bytes) -> bytes:
    assert img_bytes[:8] == b'\x89PNG\r\n\x1a\n'
    idx = img_bytes.find(b'tEXt')
    data_len = struct.unpack("!I", img_bytes[idx - 4: idx])[0]
    return img_bytes[idx + 4: idx + 4 + data_len]


if __name__ == '__main__':
    import time
    src = '打倒帝国主义.png'
    img_bytes = open(src, 'rb').read()
    t1 = time.time()
    embed_data = '中华人民共和国万岁!世界人民大团结万岁!'.encode() * 1
    out = write_text_chunk(img_bytes, embed_data)
    t2 = time.time()
    open('test.png', 'wb').write(out)

    t3 = time.time()
    read_embed_data = read_text_chunk(out)
    t4 = time.time()
    print(embed_data == read_embed_data, read_embed_data.decode())
    print('WRITE', t2 - t1)
    print('READ', t4 - t3)

Upvotes: 0

Tandera
Tandera

Reputation: 54

You can use the pypng library in Python:

#!/bin/python3

import png

reader = png.Reader(filename='input.png')
chunks = list(reader.chunks())

chunk_field = (b"tEXt", b"Profile\x00Profile Content Here")
chunks.insert(1, chunk_field)

file = open('output.png', 'wb')
png.write_chunks(file, chunks)

Upvotes: 0

cfh008
cfh008

Reputation: 456

This can be implemented with python pypng module. The python3 example code is below:

import png

TEXT_CHUNK_FLAG = b'tEXt'


def generate_chunk_tuple(type_flag, content):
    return tuple([type_flag, content])


def generate_text_chunk_tuple(str_info):
    type_flag = TEXT_CHUNK_FLAG
    return generate_chunk_tuple(type_flag, bytes(str_info, 'utf-8'))


def insert_text_chunk(target, text, index=1):
    if index < 0:
        raise Exception('The index value {} less than 0!'.format(index))

    reader = png.Reader(filename=target)
    chunks = reader.chunks()
    chunk_list = list(chunks)
    print(chunk_list[0])
    print(chunk_list[1])
    print(chunk_list[2])
    chunk_item = generate_text_chunk_tuple(text)
    chunk_list.insert(index, chunk_item)

    with open(target, 'wb') as dst_file:
        png.write_chunks(dst_file, chunk_list)


def _insert_text_chunk_to_png_test():
    src = r'E:\temp\png\register_05.png'
    insert_text_chunk(src, 'just for test!')


if __name__ == '__main__':
    _insert_text_chunk_to_png_test()

Upvotes: 8

Susam Pal
Susam Pal

Reputation: 34294

Note: This answer was a response to the original revision of the question where it was not clear if the text had to be written on the image or if the text had to be embedded within the image binary file as metadata. This answer assumed the former. However the question was edited to clarify that it meant the latter. This answer is left intact, in case someone is looking for a solution to the former.


convert -draw "text 20,20 'hello, world'" input.png output.png

The 20,20 in the above example is the co-ordinate where I want to place the text.

You need to use the imagemagick package to get this command.

On Ubuntu or Debian, it can be installed with the command: apt-get install imagemagick.

Here is a slightly more elaborate usage of the command:

convert -font Helvetica -pointsize 20 -draw "text 20,20 'hello, world'" input.png output.png

Upvotes: 5

gioele
gioele

Reputation: 10215

Use ImageMagick's convert and the -set option:

convert IN.png \
        -set 'Copyright' 'CC-BY-SA 4.0' \
        -set 'Title' 'A wonderful day' \
        -set comment 'Photo taken while running' \
        OUT.png

The -set option is used to set metadata elements. In the case of PNG these often go into tEXt chunks.

Upvotes: 20

Bruce_Warrior
Bruce_Warrior

Reputation: 1181

Adding to the comment made by @susam-pal, to change color use the option

-fill color

This option accepts a color name, a hex color, or a numerical RGB, RGBA, HSL, HSLA, CMYK, or CMYKA specification. See Color Names for a description of how to properly specify the color argument.

Enclose the color specification in quotation marks to prevent the "#" or the parentheses from being interpreted by your shell. For example,

-fill blue

-fill "#ddddff"

-fill "rgb(255,255,255)"


Obtained from link

Upvotes: 0

futureman
futureman

Reputation: 9

I believe that pngcrush has this ability: http://pwet.fr/man/linux/commandes/pngcrush

Upvotes: 0

gcbenison
gcbenison

Reputation: 11963

I have searched around for utilities to do this, and not yet found anything that really matches what I want to do. So I decided to build my own, which it turns out is not too hard. The utility png-text-dump displays all text chunks in a PNG image. It depends only on libpng. The utility png-text-append inserts text chunks into a PNG image. It depends only on the standard C library - I had initially tried to implement this using libpng, but actually found it easier to work from scratch using only the PNG specification.

Upvotes: 15

Related Questions