Example: Replace image data#

This example covers reading of a LayeredFile (A Photoshop file) after which we access the image layers that contain image data. We additionally cover how we can then extract, modify and replace that image data after which we save out the file again.

Relevant documentation links:

/*
Example of loading a PhotoshopFile and extracting the image data, modifying this and then replacing it again
*/

#include "PhotoshopAPI.h"

#include <algorithm>
#include <execution>
#include <cmath>
#include <cstdint>
#include <vector>
#include <unordered_map>


// sRGB to linear conversion
float srgbToLinear(uint8_t srgb)
{
	float normalized = srgb / 255.0f;
	if (normalized <= 0.04045f)
		return normalized / 12.92f;
	else
		return std::pow((normalized + 0.055f) / 1.055f, 2.4f);
}

int main()
{
	using namespace NAMESPACE_PSAPI;

	// In this case we already know the bit depth but otherwise one could use the PhotoshopFile.m_Header.m_Depth
	// variable on the PhotoshopFile to figure it out programmatically. This would need to be done using the 
	// "extended" read signature shown in the ExtendedSignature example.
	LayeredFile<bpp8_t> layeredFile = LayeredFile<bpp8_t>::read("ImageData.psb");

	// We could also use find_layer() on the LayeredFile but this way we directly get the appropriate type.
	// Keep in mind this can return nullptr!
	auto imageLayerPtr = find_layer_as<bpp8_t, ImageLayer>("Blue_Lagoon/Blue_Lagoon.exr", layeredFile);

	// Now we can grab all channels (we could also use just grab a single channel)
	auto channels = imageLayerPtr->get_image_data();

	// Now we do our modifications. In this example we apply a sRGB -> linear operation
	for (auto& [_, value] : channels)
	{
		std::for_each(std::execution::par, value.begin(), value.end(), [&](uint8_t& pixelValue) 
			{
				pixelValue = static_cast<uint8_t>(std::round(srgbToLinear(pixelValue) * 255.0f));
			});
	}

	// Finally we can set the image data to the channel again and save out our file
	imageLayerPtr->set_image_data(std::move(channels));
	LayeredFile<bpp8_t>::write(std::move(layeredFile), "ModifiedImageData.psb");
}
# Example of replacing image data on a layer
import os
import numpy as np
import photoshopapi as psapi


def srgb_to_linear(srgb: np.ndarray) -> np.ndarray:
    # Normalize the uint8 values to range [0, 1]
    normalized = srgb / 255.0

    # Apply the sRGB to linear transformation
    linear = np.where(normalized <= 0.04045, 
                      normalized / 12.92, 
                      np.power((normalized + 0.055) / 1.055, 2.4))
    
    # Convert back to uint8 by scaling to [0, 255]
    return np.round(linear * 255).astype(np.uint8)


def main() -> None:

    # In the python bindings we expose a wrapper which reads a LayeredFile instance with the
    # correct bit-depth
    layered_file = psapi.LayeredFile.read(os.path.join(os.path.dirname(__file__), "ImageData.psb"))

    # We can verify this by printing the type of layered_file
    print(type(layered_file))   # <--- Will print psapi.LayeredFile_8bit

    # One massive advantage is that we can use dict-like indexing to retrieve the layers
    img_layer: psapi.ImageLayer_8bit = layered_file["Blue_Lagoon"]["Blue_Lagoon.exr"]
    img_data: dict[int, np.ndarray] = img_layer.get_image_data()
    
    # Apply the sRGB to linear conversion for each channel in the image data, this could be any sort of operation
    # we could also resize the image data
    for channel, data in img_data.items():
        print(f"Processing channel: {channel}")
        img_data[channel] = srgb_to_linear(data)
        
    # Now we set the image data again and can write out the file!
    # Note that if we had rescaled the image data using opencv or similar we would have to 
    # pass the new width and height as the second and third argument!
    img_layer.set_image_data(img_data)
    layered_file.write(os.path.join(os.path.dirname(__file__), "ModifiedImageData.psb"))



if __name__ == "__main__":
    main()