Read and write Photoshop PSD and PSB files from C++, C, Rust and Python:
ffpsd/ffpsd.hpp- C++ffpsd/c_api.h- C, for other languages and toolchains- Rust - the ffpsd crate, sources in bindings/rust/
- Python - the ffpsd package, sources in bindings/python/
Until 1.0.0 the APIs are not stable: a release may break source and binary compatibility. What comes next: ROADMAP.md.
- PSD and PSB, and either into the other; RGB and gray documents, and either into the other.
- Layers: raster layers, groups, the background and Levels adjustments; added, copied from this or another document, moved and removed; name, opacity, visibility, clipping and blend mode; pixels in and out.
- Pixel masks, read, set and removed.
- The canvas and the layers resized, turned and flipped; the canvas grown or cut from any anchor.
- The composite the file stores, read and replaced; resolution and version info.
- Every image resource and tagged block, raw, for what ffpsd does not interpret itself.
- PNG, JPEG and WebP in and out.
- Single-threaded, with no global state: a document belongs to one thread at a time, and separate documents work in separate threads at once, so a batch of files spreads over every core.
- Nothing is lost: blocks, resources and channels ffpsd does not interpret are written back byte for byte, and a document saved unchanged is the file it was read from.
- Lazy: a channel is decoded when its pixels are asked for and written back as stored until they change, so opening and saving cost little more than the file's size.
- In memory: opening reads the whole file and closes it, so the file is not locked while you work; saving builds the new file in memory and writes it once. The price is memory: about the file's size for the document, twice that while opening or saving, plus any pixels you decode.
- Self-contained: libpng, zlib-ng, libjpeg-turbo and libwebp are built in under renamed symbols, so other copies in the same program do not clash, and PNG, JPEG and WebP load and save with nothing else installed.
Code style: CONTRIBUTING.md. Programs in C++, C, Rust and Python: examples/.
libpng, zlib-ng, libjpeg-turbo, libwebp, Dear ImGui, SDL3, GoogleTest and Google Benchmark are git submodules under
vendor/; the test PSD files are in Git LFS (git lfs install before cloning).
git clone --recursive <url>
git submodule update --init --recursive # in a clone made without them
git lfs pull # in a clone made without Git LFSRelease, installed into ffpsd-out/:
python build.py # shared library
python build.py --static # static libraryIt checks for CMake >= 3.23, Ninja and a compiler, sets up MSVC on Windows by
itself and fetches missing submodules. Flags: --clean, --jobs N,
--no-apps, --no-png, --no-jpeg, --no-webp, --out DIR, and on Windows
--crt static|dynamic (C runtime on Windows) and --msvc 14.29, the toolset
of an older Visual Studio installed beside the latest.
ffpsd-out/bin/ img2ffpsd-cli, img2ffpsd-ui; ffpsd.dll on Windows
ffpsd-out/lib/ libffpsd.so, libffpsd.dylib, ffpsd.lib or libffpsd.a; cmake/ffpsd/
ffpsd-out/include/ ffpsd/*.hpp, ffpsd/c_api.h, ffpsd/export.h
ffpsd-out/share/ ffpsd/licenses/
libpng, zlib-ng, libjpeg-turbo and libwebp are inside the library, the static one too, and
every symbol of theirs starts with ffpsd_: a program can link its own copies of them beside ffpsd.
libjpeg-turbo is plain C, without SIMD, and libwebp's SIMD is C intrinsics, so no assembler is needed.
Built with FFPSD_BUILD_APPS and installed into bin/:
- img2ffpsd - folders of PNG, JPEG and WebP pictures into layered PSD files:
img2ffpsd-clion the command line,img2ffpsd-uiin a window withFFPSD_WITH_IMGUI;build.pybuilds both
CMake >= 3.23 and Ninja; on Windows, from an x64 Native Tools Command Prompt.
cmake --preset shared-release
cmake --build --preset shared-release
cmake --install build/shared-release # into ffpsd-out/ unless --prefix says otherwise| Preset | Library | Build type |
|---|---|---|
static-debug |
static | Debug |
static-release |
static | Release |
shared-debug |
shared | Debug |
shared-release |
shared | Release |
bench-release |
static | Release, benchmarks only |
The debug presets build them; the script builds and runs them from any shell:
python scripts/run_tests.py # static-debug
python scripts/run_tests.py --shared # shared-debug
python scripts/run_tests.py --all # both
python scripts/run_tests.py --filter ReadPsd # by name
python scripts/run_tests.py --no-build # just runBy hand: cmake --preset static-debug, cmake --build --preset static-debug,
ctest --preset static-debug.
Documents, layers and the picture formats on the files in tests/data/photoshop/;
benchmarks/README.md lists them. The debug presets only build
them; numbers come from bench-release:
python scripts/run_benchmarks.py # everything, 10 repetitions
python scripts/run_benchmarks.py --filter Layer/ # by regular expression
python scripts/run_benchmarks.py --save before # keep this run as "before"
python scripts/run_benchmarks.py --compare before # run again and compare
python scripts/run_benchmarks.py --against v0.4.1 # build that version too, run both, compare
python scripts/run_benchmarks.py --no-build # just runRuns are kept in build/bench-release/results/. --compare and --against use Google
Benchmark's compare.py, which needs
pip install -r requirements-dev.txt.
- Release:
/O2 /Ob3 /Oi /Ot /Gy(MSVC) or-O3; LTO for the shared library where available. The static one has none, so any linker takes its objects, and it is position-independent. - Debug:
/W4 /permissive-(MSVC) or-Wall -Wextra -Wpedantic -Wshadow -Wconversion,-Wold-style-castfor C++. - No AVX or AVX2 by default, so the builds run on any x86-64 processor. With them, writing
re-encoded channels is faster:
/arch:AVX2(MSVC) or-march=nativeinCMAKE_CXX_FLAGSandCMAKE_C_FLAGS.
| Option | Default | Meaning |
|---|---|---|
BUILD_SHARED_LIBS |
OFF |
shared instead of static |
FFPSD_BUILD_APPS |
ON |
apps/ |
FFPSD_WITH_PNG |
ON |
PNG in ffpsd/formats.hpp, with libpng and zlib-ng |
FFPSD_WITH_JPEG |
ON |
JPEG in ffpsd/formats.hpp, with libjpeg-turbo |
FFPSD_WITH_WEBP |
ON |
WebP in ffpsd/formats.hpp, with libwebp |
FFPSD_WITH_IMGUI |
OFF |
the apps with a window, with Dear ImGui and SDL3 |
FFPSD_BUILD_TESTS |
OFF, ON in the debug presets |
tests/ |
FFPSD_BUILD_BENCHMARKS |
OFF, ON in bench-release and the debug presets |
benchmarks/ |
FFPSD_BUILD_EXAMPLES |
OFF, ON in the debug presets |
examples/ in C and C++ |
FFPSD_MSVC_STATIC_RUNTIME |
OFF |
MSVC: /MT instead of /MD, see C runtime on Windows |
On top of a preset: cmake --preset shared-release -DFFPSD_BUILD_APPS=OFF.
find_package(ffpsd 0.4.7 REQUIRED)
target_link_libraries(my_app PRIVATE ffpsd::ffpsd)The package defines FFPSD_STATIC for a static build, so ffpsd/export.h
picks the right linkage. On Windows the program also has to match the C runtime, below.
MSVC links the C runtime as a DLL (/MD, /MDd in Debug) or statically (/MT, /MTd).
ffpsd and the libraries inside it take /MD, as CMake gives any program;
-DFFPSD_MSVC_STATIC_RUNTIME=ON or build.py --crt static makes it /MT.
| ffpsd | The program needs |
|---|---|
| static | the same CRT and configuration, or the link fails (LNK2038) |
| shared, the C++ API | the same CRT, compiler and configuration: std::string and std::vector cross over |
| shared, the C API | any |
A shared library built with /MT has its own CRT and heap, so only the C API is safe with it;
CMake warns about it. A program that wants /MT with a static ffpsd built so:
# quoted: the shell would read < > as redirection
cmake -S . -B build '-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$<CONFIG:Debug>:Debug>'The libraries on the Releases page are Release builds with /MD, so a Debug program can use only
the C API of the shared one. The apps there are built with /MT and need no Visual C++ runtime installed.
They are built with the toolset of Visual Studio 2019, MSVC 14.29, so Visual Studio 2019 and later link them.
Adobe and Photoshop are either registered trademarks or trademarks of Adobe in the United States and/or other countries. ffpsd is not affiliated with or endorsed by Adobe.