pictomancer

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: MIT Imports: 11 Imported by: 0

README

pictomancer go-sdk

Go SDK for Pictomancer.ai - a thin client for the REST API at https://api.pictomancer.ai, built on withttp.

Install

go get github.com/pictomancer/go-sdk

Usage

package main

import (
	"context"
	"os"

	pictomancer "github.com/pictomancer/go-sdk"
)

func main() {
	client := pictomancer.NewClient(
		pictomancer.WithAPIKey("your-api-key"),
	)
	ctx := context.Background()

	info, err := client.Info(ctx)
	if err != nil {
		panic(err)
	}
	_ = info

	meta, err := client.Analyze(ctx, "https://example.com/image.jpg")
	if err != nil {
		panic(err)
	}
	_ = meta.SizeBytes

	result, err := client.Compress(ctx, "https://example.com/image.jpg", pictomancer.CompressParams{
		Format: "webp",
		Q:      80,
		Strip:  true,
	})
	if err != nil {
		panic(err)
	}
	if err := os.WriteFile("out.webp", result.Bytes, 0o644); err != nil {
		panic(err)
	}
}

Sources can be an image URL, a base64 string, or a data: URI. For local files, readers, or in-memory bytes:

source, err := pictomancer.SourceFromPath("photo.jpg")
source, err = pictomancer.SourceFromReader(file)
source = pictomancer.SourceFromBytes(data)

Timeouts and cancellation flow through context.Context.

Operations
client.Info(ctx)
client.Usage(ctx)
client.Analyze(ctx, source)
client.Resize(ctx, source, pictomancer.ResizeParams{Scale: 0.5, Format: "webp"})
client.Compress(ctx, source, pictomancer.CompressParams{Q: 80})
client.Convert(ctx, source, "avif", pictomancer.ConvertParams{Q: 50, Effort: pictomancer.Int(2)})
client.Crop(ctx, source, pictomancer.CropParams{X: pictomancer.Int(0), Y: pictomancer.Int(0), Width: pictomancer.Int(100), Height: pictomancer.Int(100), Format: "png"})
client.Pipeline(ctx, source, []pictomancer.PipelineOperation{
	{Type: "resize", Params: map[string]string{"scale": "0.5"}},
	{Type: "convert", Params: map[string]string{"format": "webp"}},
}, nil)

Operations return an OpResult: Bytes holds the optimized image for inline delivery, Receipt holds the JSON receipt for put_url/callback deliveries.

Geometry ops: smart crop, trim, fill, autorot

Breaking in v0.4.0: Crop's x, y, width, height int positional args moved into CropParams as X, Y, Width, Height *int (pointers, since 0 is a legitimate corner distinct from unset). Migrate:

// Before (< v0.4.0)
client.Crop(ctx, src, 10, 10, 100, 100, pictomancer.CropParams{})

// After (>= v0.4.0)
client.Crop(ctx, src, pictomancer.CropParams{X: pictomancer.Int(10), Y: pictomancer.Int(10), Width: pictomancer.Int(100), Height: pictomancer.Int(100)})

CropParams has three mutually exclusive modes:

// Manual: exact rectangle.
client.Crop(ctx, source, pictomancer.CropParams{X: pictomancer.Int(0), Y: pictomancer.Int(0), Width: pictomancer.Int(100), Height: pictomancer.Int(100)})

// Smart: Gravity picks the window. One of "attention", "entropy", "centre".
client.Crop(ctx, source, pictomancer.CropParams{Gravity: "attention", Width: pictomancer.Int(200), Height: pictomancer.Int(200)})

// Trim: removes a uniform background border. Threshold defaults to 10.0 server-side.
client.Crop(ctx, source, pictomancer.CropParams{Trim: true, Threshold: 5.0})

ResizeParams gains a fill mode: set Width + Height (instead of Scale/ScaleX/ScaleY) to resize and smart-crop to exact dimensions in one call; Gravity defaults to "attention".

client.Resize(ctx, source, pictomancer.ResizeParams{Width: pictomancer.Int(200), Height: pictomancer.Int(150), Gravity: "entropy"})

All four params structs (ResizeParams, CompressParams, ConvertParams, CropParams) have an Autorot bool field to apply EXIF orientation before processing.

When a crop actually trims, the response carries X-Pictomancer-Trim-Left/-Top/-Width/-Height headers (read them off the raw HTTP response if you need them; OpResult doesn't surface headers today).

Enhance: denoise, auto-contrast, sharpen

All four params structs also have Denoise int, Equalize bool and Sharpen bool, opt-in modifiers applied in a fixed order: autorot -> denoise -> equalize -> operation -> sharpen. Base price, no surcharge.

client.Convert(ctx, source, "webp", pictomancer.ConvertParams{Denoise: 2, Equalize: true})
client.Resize(ctx, source, pictomancer.ResizeParams{Scale: 0.5, Sharpen: true})

Denoise is a median filter, radius 1-3 (window 3x3 to 7x7); the server returns 422 outside that range. Equalize auto-contrasts the value channel only - hue and saturation are preserved. Sharpen runs an unsharp mask after the operation with libvips defaults. A compress that grows because of these modifiers is still billed (X-Pig-Billed: 1), unlike a plain no-gain compress.

Perceptual quality target

Instead of guessing a Q, ask compress/convert for the smallest file with SSIM >= target. The server binary-searches the encoder quality and reports the outcome in X-Pictomancer-Quality-* headers, surfaced as OpResult.Quality:

result, err := client.Compress(ctx, source, pictomancer.CompressParams{
	Format:        "webp", // required with QualityTarget
	QualityTarget: 0.95,   // 0 < v <= 1; mutually exclusive with Q
})
if err != nil {
	panic(err)
}
if result.Quality != nil {
	// result.Quality.Achieved (e.g. 0.9530), .QFinal, .Encodes
}

Supported for jpeg, webp and avif outputs; on convert it is also invalid with Lossless: true. Not available inside pipelines. Quality is nil when no search ran - either no QualityTarget was sent, or the input already met the target and came back untouched (X-Pig-Billed: 0).

AI-generated images: one call to web-ready

Image generators (gpt-image, DALL-E, Flux, Midjourney, Stable Diffusion) return 2-8 MB PNGs. optimize_generated returns the same picture as web-ready webp (default), avif, jpeg or png: metadata stripped, transparency kept, optional max_dimension cap (never upscales), optional q or quality_target. Same price as convert; a result that is not smaller is returned free.

client.OptimizeGenerated(ctx, source, pictomancer.OptimizeGeneratedParams{Format: "avif", MaxDimension: 1600})
Delivery targets
// Presigned PUT (S3/R2/GCS/Azure). No cloud credentials reach Pictomancer.
result, err := client.Compress(ctx, source, pictomancer.CompressParams{
	Format:   "webp",
	Delivery: pictomancer.NewPutURLDelivery(presignedURL),
})

// POST to your endpoint, HMAC-signed (X-Pig-Signature: sha256=<hex>).
result, err = client.Convert(ctx, source, "avif", pictomancer.ConvertParams{
	Delivery: pictomancer.NewCallbackDelivery(
		"https://hooks.example.com/pig?token=...",
		pictomancer.WithDeliverySecret(secret),
	),
})
Options
pictomancer.WithAPIKey("...")        // Bearer token
pictomancer.WithBaseURL("...")       // defaults to https://api.pictomancer.ai
pictomancer.WithAgentWallet("0x...") // X-Agent-Wallet for x402 tracking
pictomancer.WithAdapter(withttp.Fasthttp()) // swap the HTTP backend
Errors

Non-2xx responses return *pictomancer.APIError:

var apiErr *pictomancer.APIError
if errors.As(err, &apiErr) && apiErr.Status == 402 {
	// free tier exhausted: pay per request (x402) or use an API key
}

Development

go test ./...

License

MIT

Documentation

Index

Constants

View Source
const DefaultBaseURL = "https://api.pictomancer.ai"
View Source
const Version = "0.6.0"

Variables

This section is empty.

Functions

func Bool added in v0.6.0

func Bool(v bool) *bool

Bool returns a pointer to v, for optional bool params such as OptimizeGeneratedParams.Strip.

func Int

func Int(v int) *int

Int returns a pointer to v, for optional int params such as ConvertParams.Effort.

func SourceFromBytes added in v0.2.0

func SourceFromBytes(data []byte) string

SourceFromBytes encodes in-memory image bytes as the raw base64 source string the API accepts.

func SourceFromPath added in v0.2.0

func SourceFromPath(path string) (string, error)

SourceFromPath reads a local image file and encodes it.

func SourceFromReader added in v0.2.0

func SourceFromReader(r io.Reader) (string, error)

SourceFromReader reads the whole image from r and encodes it.

Types

type APIError

type APIError struct {
	Status int
	Detail string
}

APIError is returned for any non-2xx response from the API.

func (*APIError) Error

func (e *APIError) Error() string

type AnalyzeResponse

type AnalyzeResponse struct {
	SizeBytes int64 `json:"size_bytes"`
}

AnalyzeResponse is the metadata of a fetched image. Always free.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a thin client for the Pictomancer.ai REST API.

func NewClient

func NewClient(opts ...Option) *Client

func (*Client) Analyze

func (c *Client) Analyze(ctx context.Context, source string) (AnalyzeResponse, error)

func (*Client) Compress

func (c *Client) Compress(ctx context.Context, source string, params CompressParams) (OpResult, error)

func (*Client) Convert

func (c *Client) Convert(ctx context.Context, source, format string, params ConvertParams) (OpResult, error)

func (*Client) Crop

func (c *Client) Crop(ctx context.Context, source string, params CropParams) (OpResult, error)

func (*Client) Info

func (c *Client) Info(ctx context.Context) (InfoResponse, error)

func (*Client) OptimizeGenerated added in v0.6.0

func (c *Client) OptimizeGenerated(ctx context.Context, source string, params OptimizeGeneratedParams) (OpResult, error)

func (*Client) Pipeline

func (c *Client) Pipeline(ctx context.Context, source string, operations []PipelineOperation, delivery *Delivery) (OpResult, error)

func (*Client) Resize

func (c *Client) Resize(ctx context.Context, source string, params ResizeParams) (OpResult, error)

func (*Client) Usage

func (c *Client) Usage(ctx context.Context) (UsageResponse, error)

type CompressParams

type CompressParams struct {
	Format        string         `json:"format,omitempty"`
	Q             int            `json:"q,omitempty"`
	QualityTarget float64        `json:"quality_target,omitempty"`
	Strip         bool           `json:"strip,omitempty"`
	Autorot       bool           `json:"autorot,omitempty"`
	Denoise       int            `json:"denoise,omitempty"`
	Equalize      bool           `json:"equalize,omitempty"`
	Sharpen       bool           `json:"sharpen,omitempty"`
	Extra         map[string]any `json:"-"`
	Delivery      *Delivery      `json:"delivery,omitempty"`
}

CompressParams tunes the compress operation. Zero values are omitted. QualityTarget (0 < v <= 1) asks for the smallest file with SSIM >= target instead of a fixed Q; mutually exclusive with Q, requires an explicit Format (jpeg/webp/avif). The outcome lands in OpResult.Quality. Denoise (1-3), Equalize and Sharpen are opt-in enhancement modifiers.

type ConvertParams

type ConvertParams struct {
	Q             int            `json:"q,omitempty"`
	QualityTarget float64        `json:"quality_target,omitempty"`
	Strip         bool           `json:"strip,omitempty"`
	Lossless      bool           `json:"lossless,omitempty"`
	Effort        *int           `json:"effort,omitempty"`
	Autorot       bool           `json:"autorot,omitempty"`
	Denoise       int            `json:"denoise,omitempty"`
	Equalize      bool           `json:"equalize,omitempty"`
	Sharpen       bool           `json:"sharpen,omitempty"`
	Extra         map[string]any `json:"-"`
	Delivery      *Delivery      `json:"delivery,omitempty"`
}

ConvertParams tunes the convert operation. Zero values are omitted. Effort is a pointer because 0 is a valid AVIF encoder value distinct from unset (API default 2); use Int to build it. QualityTarget (0 < v <= 1) asks for the smallest file with SSIM >= target instead of a fixed Q; mutually exclusive with Q and Lossless, only for jpeg/webp/avif targets. The outcome lands in OpResult.Quality. Denoise (1-3), Equalize and Sharpen are opt-in enhancement modifiers.

type CropParams

type CropParams struct {
	X         *int           `json:"x,omitempty"`
	Y         *int           `json:"y,omitempty"`
	Width     *int           `json:"width,omitempty"`
	Height    *int           `json:"height,omitempty"`
	Format    string         `json:"format,omitempty"`
	Gravity   string         `json:"gravity,omitempty"`
	Trim      bool           `json:"trim,omitempty"`
	Threshold float64        `json:"threshold,omitempty"`
	Autorot   bool           `json:"autorot,omitempty"`
	Denoise   int            `json:"denoise,omitempty"`
	Equalize  bool           `json:"equalize,omitempty"`
	Sharpen   bool           `json:"sharpen,omitempty"`
	Extra     map[string]any `json:"-"`
	Delivery  *Delivery      `json:"delivery,omitempty"`
}

CropParams tunes the crop operation. Three mutually exclusive modes: manual (X+Y+Width+Height), smart (Gravity + Width+Height, X/Y nil), trim (Trim=true, optional Threshold, X/Y/Width/Height nil). Autorot is valid in all three. X/Y/Width/Height are pointers because 0 is a legitimate corner distinct from unset; use Int to build them. Denoise (1-3), Equalize and Sharpen are opt-in enhancement modifiers.

type Delivery

type Delivery struct {
	Mode        string            `json:"mode"`
	PutURL      string            `json:"put_url,omitempty"`
	CallbackURL string            `json:"callback_url,omitempty"`
	Headers     map[string]string `json:"headers,omitempty"`
	Secret      string            `json:"secret,omitempty"`
}

Delivery selects where the optimized bytes go. Inline (the default) returns them in the response; put_url uploads to a customer-signed presigned PUT URL; callback_url POSTs them to a customer endpoint with an X-Pig-Sha256 integrity header and optional HMAC signature.

func NewCallbackDelivery

func NewCallbackDelivery(url string, opts ...DeliveryOption) *Delivery

func NewInlineDelivery

func NewInlineDelivery() *Delivery

func NewPutURLDelivery

func NewPutURLDelivery(url string, opts ...DeliveryOption) *Delivery

type DeliveryOption

type DeliveryOption func(*Delivery)

func WithDeliveryHeaders

func WithDeliveryHeaders(headers map[string]string) DeliveryOption

WithDeliveryHeaders forwards extra signed headers (x-amz-*, x-goog-*, x-ms-*) that the presigned URL or endpoint expects.

func WithDeliverySecret

func WithDeliverySecret(secret string) DeliveryOption

WithDeliverySecret enables HMAC-SHA256 signing of callback bodies: the request carries X-Pig-Signature: sha256=<hex>, recomputable on the receiving end with the same secret. Used per request, never stored.

type FormatOption

type FormatOption struct {
	Name        string `json:"name"`
	Kind        string `json:"kind"`
	DefaultStr  string `json:"default_str"`
	Description string `json:"description"`
	Min         *int   `json:"min,omitempty"`
	Max         *int   `json:"max,omitempty"`
}

FormatOption describes one tunable knob of an output format.

type FormatSpec

type FormatSpec struct {
	ID      string         `json:"id"`
	Suffix  string         `json:"suffix"`
	Options []FormatOption `json:"options"`
}

FormatSpec describes one output format (id, file suffix, options).

type InfoResponse

type InfoResponse struct {
	Formats []FormatSpec `json:"formats"`
}

InfoResponse lists the supported output formats and their options.

type OpResult

type OpResult struct {
	Bytes   []byte
	Receipt map[string]any
	Quality *QualityReport
}

OpResult is the outcome of an image operation. Exactly one of Bytes and Receipt is populated: Bytes for inline delivery, Receipt (etag, sha256, bytes_written, ...) for put_url/callback deliveries. Quality accompanies either when the operation ran a quality_target search, nil otherwise.

type OptimizeGeneratedParams added in v0.6.0

type OptimizeGeneratedParams struct {
	Format        string         `json:"format,omitempty"`
	Q             int            `json:"q,omitempty"`
	QualityTarget float64        `json:"quality_target,omitempty"`
	MaxDimension  int            `json:"max_dimension,omitempty"`
	Strip         *bool          `json:"strip,omitempty"`
	Extra         map[string]any `json:"-"`
	Delivery      *Delivery      `json:"delivery,omitempty"`
}

OptimizeGeneratedParams tunes the optimize_generated operation. Zero values are omitted; the server defaults Format to webp and Strip to true, so Strip is a pointer (use Bool) to be able to send an explicit false.

type Option

type Option func(*Client)

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the Bearer token (Authorization: Bearer ...).

func WithAdapter

func WithAdapter(adapter withttp.Client) Option

WithAdapter swaps the HTTP backend (e.g. withttp.Fasthttp()). Defaults to net/http.

func WithAgentWallet

func WithAgentWallet(wallet string) Option

WithAgentWallet sets the X-Agent-Wallet identity for x402 tracking.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the default https://api.pictomancer.ai.

type PipelineOperation

type PipelineOperation struct {
	Type   string            `json:"type"`
	Params map[string]string `json:"params"`
}

PipelineOperation is one step of a multi-op pipeline. Params values are strings, matching the API contract (e.g. {"scale": "0.5"}).

type QualityReport added in v0.3.0

type QualityReport struct {
	Target   float64
	Achieved float64
	QFinal   int
	Encodes  int
}

QualityReport is the outcome of a quality_target SSIM search, parsed from the X-Pictomancer-Quality-* response headers. Absent (nil on OpResult) when no search ran: no quality_target requested, or the input already met the target and came back untouched (X-Pig-Billed: 0).

type ResizeParams

type ResizeParams struct {
	Scale    float64        `json:"scale,omitempty"`
	ScaleX   float64        `json:"scale_x,omitempty"`
	ScaleY   float64        `json:"scale_y,omitempty"`
	Format   string         `json:"format,omitempty"`
	Width    *int           `json:"width,omitempty"`
	Height   *int           `json:"height,omitempty"`
	Gravity  string         `json:"gravity,omitempty"`
	Autorot  bool           `json:"autorot,omitempty"`
	Denoise  int            `json:"denoise,omitempty"`
	Equalize bool           `json:"equalize,omitempty"`
	Sharpen  bool           `json:"sharpen,omitempty"`
	Extra    map[string]any `json:"-"`
	Delivery *Delivery      `json:"delivery,omitempty"`
}

ResizeParams tunes the resize operation. Zero values are omitted. Use Scale for uniform scaling or ScaleX/ScaleY for independent axes. Fill mode: set Width+Height instead, optionally with Gravity (one of "attention", "entropy", "centre"; defaults to "attention" server-side). Denoise (1-3), Equalize and Sharpen are opt-in enhancement modifiers.

type UsageResponse

type UsageResponse struct {
	Identity      string `json:"identity"`
	RequestsUsed  int    `json:"requests_used"`
	FreeTierLimit int    `json:"free_tier_limit"`
	FreeRemaining int    `json:"free_remaining"`
	IsFree        bool   `json:"is_free"`
}

UsageResponse reports request usage and free tier status for the caller's identity (wallet header or IP fallback).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL