Documentation
¶
Overview ¶
Package photo is image ingest for Go web apps: sniff the real type, decode, downscale, re-encode, thumbnail, and write the result somewhere safe.
Designed to be dropped into the same apps as go-ai, go-api, go-draw, go-wiki and go-login. The problem it solves is the one every app with an upload button hits on the same afternoon: a phone camera posts 12 MB of JPEG with the owner's GPS coordinates in the EXIF, and the app stores it verbatim.
Re-encoding rather than passing bytes through is the point. It strips EXIF (which carries location), it guarantees the stored bytes really are the image that was decoded rather than a polyglot file with a payload after the marker, and it leaves one predictable format to serve.
Index ¶
- Constants
- Variables
- func DetectContentType(raw []byte, filename string) string
- func ExtensionFor(contentType string) string
- func Fit(img image.Image, edge int) image.Image
- func LooksLikeImage(b []byte) bool
- type Image
- type Namer
- type Options
- type Saved
- type Store
- func (s *Store) Open(rel string) (*os.File, error)
- func (s *Store) Options() Options
- func (s *Store) Path(rel string) (string, error)
- func (s *Store) Remove(paths ...string) error
- func (s *Store) RemoveWithThumb(rel string) error
- func (s *Store) Root() string
- func (s *Store) Save(r io.Reader, filename string, namer Namer) (*Saved, error)
- func (s *Store) SaveImage(img *Image, namer Namer) (*Saved, error)
Constants ¶
const ( // DefaultMaxEdge keeps a photographed document readable — small print on // a receipt or a lab printout survives the downscale — while cutting a // modern phone's 4000px original to a fraction of its size. DefaultMaxEdge = 1600 // DefaultQuality trades a little fidelity for a large size reduction. DefaultQuality = 82 // DefaultThumbQuality is lower because a thumbnail is never inspected. DefaultThumbQuality = 75 )
Defaults applied when an Options field is left at zero.
Variables ¶
var ErrOutsideRoot = errors.New("photo: path outside the store root")
ErrOutsideRoot is returned when a stored path would escape the store's root directory. It is the error a crafted filename from the database produces, so it is worth logging rather than swallowing.
var ErrTooLarge = errors.New("photo: image is larger than the limit")
ErrTooLarge is returned when the input exceeds Options.MaxBytes.
var ErrUnsupportedType = errors.New("photo: unsupported image type")
ErrUnsupportedType is returned when the bytes are not an image this package can decode, and Options.KeepUnsupported is not set.
Functions ¶
func DetectContentType ¶
DetectContentType sniffs the type of a file that was not decoded as an image, falling back to the filename extension for the formats net/http does not recognise. filename may be empty.
func ExtensionFor ¶
ExtensionFor maps a content type onto a file extension, leading dot included. An unrecognised type gets ".bin" rather than a guess.
func Fit ¶
Fit scales img down so its longest side is at most edge, preserving the aspect ratio. An image already within the limit is returned untouched — upscaling a small photo only wastes bytes. An edge of zero or less returns the image unchanged.
func LooksLikeImage ¶
LooksLikeImage reports whether the magic bytes are one of the formats this package decodes.
Types ¶
type Image ¶
type Image struct {
// Data is what should be stored.
Data []byte
// ContentType of Data.
ContentType string
// Width and Height of Data. Zero for a file that was kept undecoded.
Width, Height int
// OriginalBytes is the size of the input, for reporting how much the
// upload shrank.
OriginalBytes int64
// SHA256 is the hex digest of Data — the stored bytes, not the input, so
// it can be used as a content-addressed filename.
SHA256 string
// Thumb is the thumbnail, or nil when none was asked for or the file was
// kept undecoded.
Thumb []byte
// ThumbWidth and ThumbHeight describe Thumb.
ThumbWidth, ThumbHeight int
// Recompressed reports whether Data is a re-encode. It is false when the
// original bytes were kept — either because the file was not a decodable
// image, or because re-encoding it came out larger.
Recompressed bool
}
Image is the result of processing an upload.
func Process ¶
Process decodes raw, downscales it to fit Options.MaxEdge, re-encodes it as JPEG, and optionally produces a thumbnail.
filename is used only as a hint when sniffing an undecodable file's type, and may be empty.
The original bytes are kept, rather than the re-encode, when the image did not need downscaling and the re-encode came out larger — which happens with an already-optimised image, or with noise that JPEG cannot compress. Storing the bigger of the two to satisfy a rule about re-encoding would be the wrong trade, and the dimensions reported in that case are the original's, since that is what is stored.
An image that *was* downscaled keeps its re-encode even so: the point of the resize is to stop serving a 4000px original, and a format that happened to compress the big version better does not change that. Options.ForceRecompress drops the fallback entirely, for an app that needs every stored image to be one this package produced.
type Namer ¶
Namer decides where a processed image is stored, as a slash-separated path relative to the store root, extension included. It is called after processing, so Image.SHA256 and Image.ContentType are available to it.
func ContentAddressed ¶
ContentAddressed names a file by the digest of its stored bytes, under dir (which may be empty for the store root).
Identical uploads collapse onto one file, which is usually what an app wants — and is why an app using it must count the rows pointing at a name before deleting the file behind it.
type Options ¶
type Options struct {
// MaxEdge is the longest side of the stored image. Images already within
// it are not upscaled. Zero means DefaultMaxEdge; a negative value
// disables downscaling.
MaxEdge int
// ThumbEdge is the longest side of the thumbnail. Zero means no thumbnail
// is produced.
ThumbEdge int
// Quality is the JPEG quality of the stored image (1-100). Zero means
// DefaultQuality.
Quality int
// ThumbQuality is the JPEG quality of the thumbnail. Zero means
// DefaultThumbQuality.
ThumbQuality int
// MaxBytes rejects an input larger than this. Zero means no limit.
MaxBytes int64
// ForceRecompress stores the re-encode even when it is larger than the
// input, so every stored image is one this package produced.
//
// Set it where the guarantee matters more than the bytes: a re-encode is
// what strips EXIF, so an app that promises uploaded photos carry no
// location data cannot afford the fallback that keeps a phone's original
// JPEG because it happened to be smaller.
ForceRecompress bool
// KeepUnsupported stores a file that could not be decoded as an image
// byte-for-byte instead of rejecting it, so an app that accepts PDFs
// alongside photos does not lose the document.
//
// It is off by default: keeping bytes nobody parsed is a decision an app
// should make deliberately, and one that comes with serving them back
// under a Content-Type the app chose rather than the uploader.
KeepUnsupported bool
}
Options controls how an image is processed.
The zero value is usable: 1600px longest edge, quality 82, no thumbnail, no size limit, and an unsupported file rejected.
type Saved ¶
type Saved struct {
*Image
// RelPath is the stored image's path relative to the store root, in slash
// form. It is what an app should persist.
RelPath string
// ThumbPath is the thumbnail's path, or empty when none was produced.
ThumbPath string
}
Saved is an image that has been written to disk.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store writes processed images beneath a single root directory.
It owns the filesystem side only. Which user an image belongs to, and what it is of, are the app's business and belong in the app's database — the store is handed a Namer and told where to put the bytes.
func (*Store) Path ¶
Path resolves a stored relative path to an absolute one, refusing anything that escapes the root.
A stored name comes back out of a database, so it is not trusted input. A name that tries to climb out is rejected rather than quietly rewritten into something inside the root: silently rewriting turns a sign of tampering into a successful read of an unrelated file.
func (*Store) Remove ¶
Remove deletes stored files, ignoring empty names and missing files.
It returns the first real error, having attempted every path — a failure to delete one image should not leave the rest behind.
func (*Store) RemoveWithThumb ¶
RemoveWithThumb deletes a stored image together with its thumbnail.