Read the file again / original
DEVELOPMENT JOURNAL / 20
Photoshot
CODE × UI
Building a free Photoshop alternative

Read the file again

Opening a PSD or PSB is only the first check. This Photoshot journal asks what editing information survives saving and reopening. We created two layers from a new AI portrait and tested each format separately.

PHOTOSHOT DEVELOPMENT JOURNAL / 20

AI-generated study photograph. Editor captures and outputs show Photoshot in use.

PhotoshotDEVELOPMENT JOURNAL / 20FOUNDATION

From the source.

Opening and preserving editability differ

A visible composite does not prove the original structure survived: the file could have become one flattened image. Names, order, visibility, opacity, masks, text and effects need separate checks. This demonstration focuses on basic properties of two image layers.

Production psd-codec.ts uses the reader and writer in vendor/psd-codec.mjs and adapts their data to Photoshot’s document model. Format parsing and application integration are distinct work. This production working copy is not presented as identical to the public repository’s support scope.

Metadata still needs interpretation

psd-metadata.ts recursively retains ordinary objects and wraps byte arrays with Base64 and type information. It excludes canvas, imageData, rawData, children and related entries from this metadata copy. Some data is reconstructed through separate paths rather than copied indiscriminately.

Retained metadata alone does not prove an effect renders identically or every text feature remains editable. psd-resources.ts also validates resource boundaries and lengths. Reading format information and implementing its meaning are separate verification tasks.

app/studio/psd-codec.ts · SHA-256 9574fa451bac · L22–26Photoshot implementation ↗
import {readNativeWorkspaces,writeNativeWorkspaces} from './psd-workspaces.ts';
import * as ag from './vendor/psd-codec.mjs';
import {supportedColorModes} from './vendor/psd-codec.mjs';
import instantiate from './vendor/lcms/lcms.js';
import {blankDoc,makeLayer,surface,layerSurface,maskSurface,composite,uid,supportedSize,MAX_LAYERS,type Layer,type StudioDoc,type LayerMask} from './document.ts';
PhotoshotDEVELOPMENT JOURNAL / 20IMPLEMENTATION

Inside the
calculation.

Unsupported adjustments have an explicit boundary

When an adjustment cannot be mapped for PSD export, the code throws an error directing the user to preserve it as a Photoshot project. It does not silently substitute a superficially similar effect and claim complete compatibility. Keeping a .layerstudio original remains useful.

Choosing PSB does not remove application workspace limits. The reader checks the 16,384px/64MP workspace boundary, and precision workflows have additional constraints. A format’s maximum specification differs from the application’s supported processing range.

Both formats returned the two layers

On October 22, 2026, a 1536×1024 AI portrait became two layers: Portrait base and Hidden backup 60. The upper duplicate was hidden at 60% opacity. After retaining a project original, we used Save As PSD and Save As PSB, then reopened each with Open PSD / PSB.

Names, ordering, visibility and opacity 0.6 survived both imports. File headers independently showed PSD version 1 and PSB version 2, with the same dimensions and 8-bit depth. Today’s direct route was Photoshot → file → Photoshot. It is not presented as a fresh Adobe Photoshop readback test.

PHOTOSHOT / SOURCE NOTES20

Read the code.

app/studio/psd-metadata.ts · SHA-256 1e4349e5ee5e · L3–4Photoshot implementation ↗
export function packMetadata(v:any):any{if(v===null||typeof v!=='object')return v;if(ArrayBuffer.isView(v))return {__bytes:bytes64(new Uint8Array(v.buffer,v.byteOffset,v.byteLength)),__type:v.constructor.name};if(v instanceof ArrayBuffer)return {__bytes:bytes64(new Uint8Array(v)),__type:'ArrayBuffer'};if(Array.isArray(v))return v.map(packMetadata);const out:any={};for(const [k,x]of Object.entries(v))if(!['canvas','imageData','rawData','rawCompositeData','children','thumbnail','imageDataRaw'].includes(k))out[k]=packMetadata(x);return out;}
export function unpackMetadata(v:any):any{if(!v||typeof v!=='object')return v;if(v.__bytes){const a=from64(v.__bytes);return v.__type==='ArrayBuffer'?a.buffer:v.__type==='Uint16Array'?new Uint16Array(a.buffer):v.__type==='Float32Array'?new Float32Array(a.buffer):a;}if(Array.isArray(v))return v.map(unpackMetadata);return Object.fromEntries(Object.entries(v).map(([k,x])=>[k,unpackMetadata(x)]));}
app/studio/psd-resources.ts · SHA-256 405e8d1a13ef · L2–4Photoshot implementation ↗
export function resourceRange(bytes:Uint8Array){const v=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength),lengthOffset=30+v.getUint32(26),start=lengthOffset+4,end=start+v.getUint32(lengthOffset);if(end>bytes.length)throw Error('Truncated PSD image resources.');return {lengthOffset,start,end};}
export function resourceBlocks(bytes:Uint8Array){const {start,end}=resourceRange(bytes),v=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength),out:{id:number;start:number;end:number;data:Uint8Array}[]=[];let at=start;while(at+12<=end){const first=at,id=v.getUint16(at+4),name=bytes[at+6];at+=6+Math.ceil((name+1)/2)*2;if(at+4>end)throw Error('Invalid PSD resource name.');const length=v.getUint32(at);at+=4;if(at+length>end)throw Error('Invalid PSD resource length.');const data=bytes.subarray(at,at+length);at+=length+(length%2);out.push({id,start:first,end:at,data});}return out;}
export function readResource(bytes:Uint8Array,id:number){return resourceBlocks(bytes).find(b=>b.id===id)?.data;}
app/studio/psd-codec.ts · SHA-256 9574fa451bac · L63–63Photoshot implementation ↗
 if(l.adjustment){const mapped=adjustmentOut(l.adjustment,out.adjustment);if(mapped){out.adjustment=l.psdAdjustmentOriginal===JSON.stringify(l.adjustment)?out.adjustment:mapped;out.clipping=l.adjustment.scope==='clipped';}else throw Error('PSD export does not yet support '+l.adjustment.type+' adjustment layers. Save a Photoshot project to preserve them.');}
PhotoshotDEVELOPMENT JOURNAL / 20IN PRACTICE

In the editor.

Read the file again / workspace
Actual Photoshot workspace. Settings and the image are shown together.

Test fixtures do not certify every file

The stored precision-psd-render report records eight passing PSD/PSB combinations across RGB16, RGB32, CMYK16 and Lab16. Its file date is October 13, 2026. This historical engine report was not rerun today and is distinct from the portrait’s basic 8-bit layer demonstration.

The feature matrix retains limitations for text styles, effect combinations and full Adobe rendering equivalence. The model is AI-generated; workspace images are actual editor captures. Part 21 continues with the document structures behind layer groups, effects and masks.

PhotoshotDEVELOPMENT JOURNAL / 20BEFORE / AFTER

Original and reopened PSD.

Read the file again / original
01 / AI-generated demonstration original
Read the file again / result
02 / PSD reopened · two layers, hidden state and 60% retained

AI-generated study photograph. Editor captures and outputs show Photoshot in use.