package mrktplaats import ( "bytes" "image" "image/jpeg" _ "image/jpeg" // register JPEG decoder for image.Decode "io" ) // maxImageUploadBytes is the threshold above which we attempt to compress an // image before sending it to the API. The marktplaats endpoints reject payloads // that are too large (HTTP 413 / LIMIT_UNEXPECTED_FILE). const maxImageUploadBytes = 900 * 1024 // 900 KB // autoCompressImage reads all bytes from r and, if the result exceeds // maxImageUploadBytes, decodes it as an image and re-encodes it as a JPEG at // progressively smaller dimensions until it fits. If the data cannot be decoded // as a recognised image format it is returned unchanged (the server will then // return whatever error it normally would). The original data is also returned // unchanged if it is already within the size limit. func autoCompressImage(r io.Reader) ([]byte, error) { data, err := io.ReadAll(r) if err != nil { return nil, err } if len(data) <= maxImageUploadBytes { return data, nil } src, _, err := image.Decode(bytes.NewReader(data)) if err != nil { // Not a format we can handle — return as-is. return data, nil } for maxDim := 1200; maxDim >= 300; maxDim = maxDim * 3 / 4 { scaled := scaleImageNN(src, maxDim) var buf bytes.Buffer if err := jpeg.Encode(&buf, scaled, &jpeg.Options{Quality: 80}); err != nil { return data, nil // encoding failed, return original } if buf.Len() <= maxImageUploadBytes { return buf.Bytes(), nil } } return data, nil // could not get small enough, let the server handle it } // scaleImageNN scales src down so that neither dimension exceeds maxDim, // preserving the aspect ratio using nearest-neighbour sampling. // Returns src unchanged if it already fits within maxDim×maxDim. func scaleImageNN(src image.Image, maxDim int) image.Image { bounds := src.Bounds() w, h := bounds.Dx(), bounds.Dy() dstW, dstH := w, h if dstW > maxDim { dstH = dstH * maxDim / dstW dstW = maxDim } if dstH > maxDim { dstW = dstW * maxDim / dstH dstH = maxDim } if dstW == w && dstH == h { return src } dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH)) for y := 0; y < dstH; y++ { srcY := bounds.Min.Y + y*h/dstH for x := 0; x < dstW; x++ { srcX := bounds.Min.X + x*w/dstW dst.Set(x, y, src.At(srcX, srcY)) } } return dst }