November 16, 2023
Written by: 

Accessing Image Region Metadata in PHP

An image file (e.g. JPEG, PNG) contains metadata, i.e. information about the image, e.g. which camera model was used or when the picture has been taken. This information is usually stored at the beginning of the file. Three main formats, or bags of metadata, can coexist in a file and the information they contain partly overlap:

The IPTC IIM format (often just called IPTC format) and the Exif format represent sets of key-value pairs, whereas the newer XMP format is an XML representation of a more complex RDF graph. The XMP Specification Part 3 specifies how the XMP metadata are to be serialized and stored in each image file format (e.g. JPEG, PNG).

The IPTC council has defined a standard for storing Image Regions in XMP. Image Regions are useful for describing specific areas of the image (e.g. objects, people) or for indicating how the image should be cropped or rotated to best fit a given container. The Frameright app can be used to define such Image Regions and insert them in the metadata of a picture.

This tutorial shows how to read the Image Region metadata of an image in PHP. This is especially useful if you want to access image metadata from within a WordPress plugin or a backend application implemented using the LAMP stack.

Setting up the environment

Let’s install the following tools:

  • a PHP interpreter;
  • Composer, a dependency manager for PHP.

On Ubuntu 22.04 for example these tools can be installed with:

sudo apt update
sudo apt install php-cli composer
    

These tools are also available on macOS and Windows. The rest of this tutorial assumes you are running a bash terminal.

Setting up the project

PHP ships a few functions natively for parsing image metadata, however they don’t extract the Image Region metadata we are interested in:

For this reason we will make use of the Frameright/php-image-metadata-parser library, which is a fork of the dchesterton/image library.

The IPTC Photo Metadata Standard 2021.1 Reference Image file contains several recently-defined XMP metadata items including Image Regions. Let’s download it with:

mkdir -p /tmp/frameright
cd /tmp/frameright
curl -O \
  https://iptc.org/std/photometadata/examples/IPTC-PhotometadataRef-Std2021.1.jpg
    

Let’s pull the library inside our project with:

composer require frameright/image-metadata-parser
    

Accessing the XMP metadata

Let’s start first by extracting and dumping all the XMP metadata. Let’s create a PHP script:

#!/usr/bin/env php
<?php
// /tmp/frameright/myscript.php

// load the pulled library:
require __DIR__ . '/vendor/autoload.php';

use CSD\Image\Image;

$image = Image::fromFile('IPTC-PhotometadataRef-Std2021.1.jpg');
$xmp_metadata = $image->getXmp();

// nicely format output with indentation and extra space:
$xmp_metadata->setFormatOutput(true);

print_r($xmp_metadata->getString());
    

We can now make it executable and run it with:

chmod a+x myscript.php
./myscript.php
    

Simplified output:

<?xml version="1.0" encoding="UTF-8"?>
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Image::ExifTool 12.41">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/" rdf:about="">
    <!-- ... -->
    </rdf:Description>
    <rdf:Description xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/" xmlns:exif="http://ns.adobe.com/exif/1.0/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about="">
      <Iptc4xmpExt:ImageRegion>
        <rdf:Bag>
          <rdf:li rdf:parseType="Resource">
            <Iptc4xmpExt:RegionBoundary rdf:parseType="Resource">
              <Iptc4xmpExt:rbH>0.385</Iptc4xmpExt:rbH>
              <Iptc4xmpExt:rbShape>rectangle</Iptc4xmpExt:rbShape>
              <Iptc4xmpExt:rbUnit>relative</Iptc4xmpExt:rbUnit>
              <Iptc4xmpExt:rbW>0.127</Iptc4xmpExt:rbW>
              <Iptc4xmpExt:rbX>0.31</Iptc4xmpExt:rbX>
              <Iptc4xmpExt:rbY>0.18</Iptc4xmpExt:rbY>
            </Iptc4xmpExt:RegionBoundary>
            <Iptc4xmpExt:rRole>
              <rdf:Bag>
                <rdf:li rdf:parseType="Resource">
                  <Iptc4xmpExt:Name>
                    <rdf:Alt>
                      <rdf:li xml:lang="x-default">Region Boundary Content Role Name (ref2021.1)</rdf:li>
                    </rdf:Alt>
                  </Iptc4xmpExt:Name>
                  <xmp:Identifier>
                    <rdf:Bag>
                      <rdf:li>https://example.org/rrole/role2021.1a</rdf:li>
                      <rdf:li>https://example.org/rrole/role2021.1b</rdf:li>
                    </rdf:Bag>
                  </xmp:Identifier>
                </rdf:li>
              </rdf:Bag>
            </Iptc4xmpExt:rRole>
          </rdf:li>
        </rdf:Bag>
      </Iptc4xmpExt:ImageRegion>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end='w'?>
    

Let’s now adapt our script to only parse the Image Regions:

// ...
$image = Image::fromFile('IPTC-PhotometadataRef-Std2021.1.jpg');
$xmp_metadata = $image->getXmp();

$regions = $xmp_metadata->getImageRegions();
print_r($regions);
    

Running the script now gives us:

Array
(
    [0] => CSD\Image\Metadata\Xmp\ImageRegion Object
        (
            [id] => persltr2
            [names] => Array
                (
                    [0] => Listener 1
                )

            [types] => Array
                (
                    [0] => Region Boundary Content Type Name (ref2021.1)
                    [1] => https://example.org/rctype/type2021.1a
                    [2] => https://example.org/rctype/type2021.1b
                )

            [roles] => Array
                (
                    [0] => Region Boundary Content Role Name (ref2021.1)
                    [1] => https://example.org/rrole/role2021.1a
                    [2] => https://example.org/rrole/role2021.1b
                )

            [rbShape] => rectangle
            [rbUnit] => relative
            [rbXY] => CSD\Image\Metadata\Xmp\Point Object
                (
                    [rbX] => 0.31
                    [rbY] => 0.18
                )

            [rbH] => 0.385
            [rbW] => 0.127
            [rbRx] => 
            [rbVertices] => 
        )

    [1] => CSD\Image\Metadata\Xmp\ImageRegion Object
        (
            [id] => persltr3
            [names] => Array
                (
                    [0] => Listener 2
                )

            [types] => Array
                (
                    [0] => Region Boundary Content Type Name (ref2021.1)
                    [1] => https://example.org/rctype/type2021.1a
                    [2] => https://example.org/rctype/type2021.1b
                )

            [roles] => Array
                (
                    [0] => Region Boundary Content Role Name (ref2021.1)
                    [1] => https://example.org/rrole/role2021.1a
                    [2] => https://example.org/rrole/role2021.1b
                )

            [rbShape] => circle
            [rbUnit] => relative
            [rbXY] => CSD\Image\Metadata\Xmp\Point Object
                (
                    [rbX] => 0.59
                    [rbY] => 0.426
                )

            [rbH] => 
            [rbW] => 
            [rbRx] => 0.068
            [rbVertices] => 
        )

    [2] => CSD\Image\Metadata\Xmp\ImageRegion Object
        (
            [id] => persltr1
            [names] => Array
                (
                    [0] => Speaker 1
                )

            [types] => Array
                (
                    [0] => Region Boundary Content Type Name (ref2021.1)
                    [1] => https://example.org/rctype/type2021.1a
                    [2] => https://example.org/rctype/type2021.1b
                )

            [roles] => Array
                (
                    [0] => Region Boundary Content Role Name (ref2021.1)
                    [1] => https://example.org/rrole/role2021.1a
                    [2] => https://example.org/rrole/role2021.1b
                )

            [rbShape] => polygon
            [rbUnit] => relative
            [rbXY] => CSD\Image\Metadata\Xmp\Point Object
                (
                    [rbX] => 
                    [rbY] => 
                )

            [rbH] => 
            [rbW] => 
            [rbRx] => 
            [rbVertices] => Array
                (
                    [0] => CSD\Image\Metadata\Xmp\Point Object
                        (
                            [rbX] => 0.05
                            [rbY] => 0.713
                        )

                    [1] => CSD\Image\Metadata\Xmp\Point Object
                        (
                            [rbX] => 0.148
                            [rbY] => 0.041
                        )

                    [2] => CSD\Image\Metadata\Xmp\Point Object
                        (
                            [rbX] => 0.375
                            [rbY] => 0.863
                        )

                )

        )

)
    

Passing the metadata to a front-end

Going one step further, as a PHP back-end developer, you now probably want to pass the image regions you have parsed on the back-end to a component on the front-end, like the Image Display Control Web Component, which takes the metadata as a JSON-formatted string.

Let’s adapt our script to produce such a string:

#!/usr/bin/env php
<?php
// /tmp/frameright/myscript.php

// load the pulled library:
require __DIR__ . '/vendor/autoload.php';

use CSD\Image\Image;
use CSD\Image\Metadata\Xmp\ShapeFilter;

$image = Image::fromFile('IPTC-PhotometadataRef-Std2021.1.jpg');
$regions = $image->getIDCMetadata(
    /* As of today, the web component supports only rectangle image
       regions. Circles and polygons aren't supported yet. */
    ShapeFilter::RECTANGLE
);
$regions_json = json_encode($regions, JSON_PRETTY_PRINT);
print($regions_json);
    

Running the script now gives us:

[
    {
        "id": "persltr2",
        "names": [
            "Listener 1"
        ],
        "shape": "rectangle",
        "unit": "relative",
        "x": "0.31",
        "y": "0.18",
        "width": "0.127",
        "height": "0.385"
    }
]
    

Which is exactly the format accepted by the web component:

<img
  id="myimg"
  is="image-display-control"
  src="assets/pics/iptc/IPTC-PhotometadataRef-Std2021.1.jpg"
  data-image-regions='[{
    "id": "persltr2",
    "names": ["Listener 1"],
    "shape": "rectangle",
    "unit": "relative",
    "x": "0.31",
    "y": "0.18",
    "width": "0.127",
    "height": "0.385"
  }]'
/>
    

Summary

In this tutorial we have learned how to read the Image Region XMP metadata of an image with PHP by pulling and using a third-party library.