Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc3667ba62 |
+7
-9
@@ -17,8 +17,8 @@ COPY . .
|
|||||||
RUN golangci-lint run --config .golangci.yml ./...
|
RUN golangci-lint run --config .golangci.yml ./...
|
||||||
|
|
||||||
# Test phase. script/test builds it alone.
|
# Test phase. script/test builds it alone.
|
||||||
# golang:1.25.4-alpine3.22, 2026-02-25; the runtime stage uses Alpine 3.22 too
|
# golang:1.25.4-alpine, 2026-02-25
|
||||||
FROM golang:1.25.4-alpine3.22@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS test
|
FROM golang:1.25.4-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS test
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
@@ -40,8 +40,8 @@ RUN go test -count=1 -timeout 90s -race -cover ./... || \
|
|||||||
# Build stage. Nothing is wanted from the two phases above: these copies
|
# Build stage. Nothing is wanted from the two phases above: these copies
|
||||||
# make BuildKit build them first, so this stage runs only when lint and
|
# make BuildKit build them first, so this stage runs only when lint and
|
||||||
# test passed.
|
# test passed.
|
||||||
# golang:1.25.4-alpine3.22, 2026-02-25; the runtime stage uses Alpine 3.22 too
|
# golang:1.25.4-alpine, 2026-02-25
|
||||||
FROM golang:1.25.4-alpine3.22@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS builder
|
FROM golang:1.25.4-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS builder
|
||||||
|
|
||||||
COPY --from=lint /src/go.sum /dev/null
|
COPY --from=lint /src/go.sum /dev/null
|
||||||
COPY --from=test /src/go.sum /dev/null
|
COPY --from=test /src/go.sum /dev/null
|
||||||
@@ -77,11 +77,9 @@ RUN version="${VERSION:-$(git describe --tags --always)}"; \
|
|||||||
-o /pixad ./cmd/pixad
|
-o /pixad ./cmd/pixad
|
||||||
|
|
||||||
# Runtime stage, and the last one: a plain `docker build .` builds this
|
# Runtime stage, and the last one: a plain `docker build .` builds this
|
||||||
# stage and what it depends on, and nothing else. It must use the Alpine
|
# stage and what it depends on, and nothing else.
|
||||||
# release the golang image above is based on, so that pixad runs against
|
# alpine:3.21, 2026-02-25
|
||||||
# the libvips and musl it was built with.
|
FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
||||||
# alpine:3.22, 2026-10-08
|
|
||||||
FROM alpine:3.22@sha256:5291449c3df73caf6ed85e649dec1b9e818b39a5d8c871e97afc13e9cd5e8fa8
|
|
||||||
|
|
||||||
# Install runtime dependencies only. vips-jxl is libvips' JPEG XL
|
# Install runtime dependencies only. vips-jxl is libvips' JPEG XL
|
||||||
# support, without which pixad does not start.
|
# support, without which pixad does not start.
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ or with 1 when images were still being processed after those 5 seconds or
|
|||||||
another part of pixa failed to stop. A request not finished by then is cut off.
|
another part of pixa failed to stop. A request not finished by then is cut off.
|
||||||
`docker stop` waits 10 seconds before it kills the container.
|
`docker stop` waits 10 seconds before it kills the container.
|
||||||
|
|
||||||
Outside Docker, pixa needs libvips (the image has 8.16) and libheif to run, as
|
Outside Docker, pixa needs libvips (the image has 8.15) and libheif to run, as
|
||||||
it uses libvips through CGO. pixad does not start unless libvips has its JPEG XL
|
it uses libvips through CGO. pixad does not start unless libvips has its JPEG XL
|
||||||
support, which on Alpine is the `vips-jxl` package and which the nix and brew
|
support, which on Alpine is the `vips-jxl` package and which the nix and brew
|
||||||
packages of libvips include, as do the apt ones from Debian 12 and Ubuntu 24.04
|
packages of libvips include, as do the apt ones from Debian 12 and Ubuntu 24.04
|
||||||
@@ -357,9 +357,9 @@ turned off.
|
|||||||
image with no profile as sRGB. Colours outside sRGB, such as the most
|
image with no profile as sRGB. Colours outside sRGB, such as the most
|
||||||
saturated ones in a Display P3 photo, are clipped.
|
saturated ones in a Display P3 photo, are clipped.
|
||||||
- The one exception is JPEG XL with libvips 8.16 and later: libvips then writes
|
- The one exception is JPEG XL with libvips 8.16 and later: libvips then writes
|
||||||
an EXIF block of its own into the image, as govips cannot ask libvips to leave
|
an EXIF block of its own into the image, with its orientation, resolution,
|
||||||
it out. It holds the image's size and otherwise fixed values, such as an
|
size and colour space and fixed defaults, but nothing from the source's EXIF,
|
||||||
orientation of 1 and a resolution of 72 dpi, and nothing from the source.
|
as govips cannot ask libvips to leave it out.
|
||||||
|
|
||||||
### Source Hosts
|
### Source Hosts
|
||||||
|
|
||||||
|
|||||||
@@ -40,17 +40,12 @@ P2: security: per-IP rate limiting on the image routes
|
|||||||
libvips ignores the quality: pixa turns `q` into a distance with libvips' own
|
libvips ignores the quality: pixa turns `q` into a distance with libvips' own
|
||||||
formula, keeping 100 lossy, and removes the metadata from the image before
|
formula, keeping 100 lossy, and removes the metadata from the image before
|
||||||
saving, as govips cannot have libvips strip it from JPEG XL. libvips 8.16 and
|
saving, as govips cannot have libvips strip it from JPEG XL. libvips 8.16 and
|
||||||
later still write an EXIF block of their own into JPEG XL, from the image's
|
later still write an EXIF block of their own into JPEG XL (orientation,
|
||||||
size, orientation and resolution, and fixed values; the image is upright and
|
resolution, size, colour space, fixed defaults), with nothing from the source.
|
||||||
is given 72 dpi before the save, so the block holds nothing from the source.
|
|
||||||
An image with an ICC profile is converted to sRGB before the JPEG XL save too,
|
An image with an ICC profile is converted to sRGB before the JPEG XL save too,
|
||||||
and a CMYK image with none is converted to sRGB, as libvips cannot save CMYK
|
and a CMYK image with none is converted to sRGB, as libvips cannot save CMYK
|
||||||
as JPEG XL. libvips' default effort, 7, is kept. JPEG XL is not yet the
|
as JPEG XL. libvips' default effort, 7, is kept. JPEG XL is not yet the
|
||||||
default output.
|
default output.
|
||||||
- 2026-10-08 pixad runs on the Alpine release it is built on (closes #229): the
|
|
||||||
runtime stage of the `Dockerfile` uses `alpine:3.22`, the release of the
|
|
||||||
`golang:1.25.4-alpine3.22` image that the test phase and the build stage use,
|
|
||||||
so all three have libvips 8.16, where the runtime image had 8.15.
|
|
||||||
- 2026-10-08 requests no longer wait behind eviction queries that read a whole
|
- 2026-10-08 requests no longer wait behind eviction queries that read a whole
|
||||||
table (closes #227): the new `cache_usage` table holds the total cache usage,
|
table (closes #227): the new `cache_usage` table holds the total cache usage,
|
||||||
kept up to date by triggers on `source_content` and `variant_content` in the
|
kept up to date by triggers on `source_content` and `variant_content` in the
|
||||||
|
|||||||
@@ -557,11 +557,6 @@ func (p *ImageProcessor) encode(
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// jxlResolution is the resolution every JPEG XL image is saved with, in
|
|
||||||
// pixels per millimetre as libvips counts it: 72 dpi, what libvips gives a
|
|
||||||
// JPEG that names none.
|
|
||||||
const jxlResolution = 72 / 25.4
|
|
||||||
|
|
||||||
// exportJXL encodes img as JPEG XL at quality, with libvips' default effort
|
// exportJXL encodes img as JPEG XL at quality, with libvips' default effort
|
||||||
// and without metadata. govips sends libvips a distance, the JPEG XL
|
// and without metadata. govips sends libvips a distance, the JPEG XL
|
||||||
// encoder's own measure of quality, along with the quality, and libvips then
|
// encoder's own measure of quality, along with the quality, and libvips then
|
||||||
@@ -579,7 +574,9 @@ func exportJXL(img *vips.ImageRef, quality int) ([]byte, error) {
|
|||||||
|
|
||||||
// govips cannot make libvips strip metadata from JPEG XL, so it is
|
// govips cannot make libvips strip metadata from JPEG XL, so it is
|
||||||
// removed from the image itself. RemoveMetadata removes EXIF, XMP and
|
// removed from the image itself. RemoveMetadata removes EXIF, XMP and
|
||||||
// IPTC but keeps the ICC profile.
|
// IPTC but keeps the ICC profile. libvips 8.16 and later still write an
|
||||||
|
// EXIF block of their own: orientation, resolution, size, colour space
|
||||||
|
// and fixed defaults.
|
||||||
err := img.RemoveMetadata()
|
err := img.RemoveMetadata()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -590,22 +587,11 @@ func exportJXL(img *vips.ImageRef, quality int) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// libvips 8.16 and later still write an EXIF block of their own, from
|
|
||||||
// the image's size, orientation and resolution, and fixed values. The
|
|
||||||
// image is upright, so its orientation is 1, but its resolution is still
|
|
||||||
// the source's.
|
|
||||||
toSave, err := img.CopyChangingResolution(jxlResolution, jxlResolution)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer toSave.Close()
|
|
||||||
|
|
||||||
params := vips.NewJxlExportParams()
|
params := vips.NewJxlExportParams()
|
||||||
params.Quality = quality
|
params.Quality = quality
|
||||||
params.Distance = jxlDistance(quality)
|
params.Distance = jxlDistance(quality)
|
||||||
|
|
||||||
output, _, err := toSave.ExportJxl(params)
|
output, _, err := img.ExportJxl(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,30 +102,6 @@ func TestImageProcessor_JPEGXLDropsSourceEXIF(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestImageProcessor_JPEGXLDropsSourceResolution verifies that the source's
|
|
||||||
// resolution does not reach the EXIF block libvips 8.16 and later write into
|
|
||||||
// JPEG XL. A JPEG XL image holds its resolution in that block alone, so the
|
|
||||||
// resolution the output loads with is the block's.
|
|
||||||
func TestImageProcessor_JPEGXLDropsSourceResolution(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// dpi-300.jpg is a flat 8x8 grey image with a resolution of 300 dpi.
|
|
||||||
input, err := os.ReadFile("testdata/dpi-300.jpg")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to read test JPEG: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output := processAndDecode(t, input, &Request{Format: FormatJXL})
|
|
||||||
|
|
||||||
// libvips gives the resolution in pixels per millimetre.
|
|
||||||
xDPI := math.Round(output.ResX() * 25.4)
|
|
||||||
yDPI := math.Round(output.ResY() * 25.4)
|
|
||||||
|
|
||||||
if xDPI == 300 || yDPI == 300 {
|
|
||||||
t.Errorf("output resolution = %vx%v dpi, the source's", xDPI, yDPI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestImageProcessor_JPEGXLAppliesEXIFOrientation verifies that a JPEG XL
|
// TestImageProcessor_JPEGXLAppliesEXIFOrientation verifies that a JPEG XL
|
||||||
// output is turned upright, as TestImageProcessor_AppliesEXIFOrientation does
|
// output is turned upright, as TestImageProcessor_AppliesEXIFOrientation does
|
||||||
// for PNG.
|
// for PNG.
|
||||||
|
|||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 799 B |
Reference in New Issue
Block a user