Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use VIPS #267

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/check-on-pr-conversion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# by the GNU Affero General Public License v3.0 only, included in the file
# licenses/AGPL.txt.

name: Lin and build voltaserve/conversion
name: Lint and build voltaserve/conversion

on:
pull_request:
Expand All @@ -21,6 +21,9 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libvips-dev

- name: Checkout Repository
uses: actions/checkout@v4

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/check-on-pr-mosaic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libvips-dev

- name: Checkout Repository
uses: actions/checkout@v4

Expand Down
2 changes: 1 addition & 1 deletion api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ FROM golang:1.22-alpine AS builder

WORKDIR /build

COPY go.mod go.sum .
COPY go.mod go.sum ./

RUN go mod download

Expand Down
7 changes: 5 additions & 2 deletions conversion/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@

FROM golang:1.22-bookworm AS builder

RUN apt-get update &&\
apt-get install -y libvips-dev

WORKDIR /build

COPY go.mod go.sum .
COPY go.mod go.sum ./

RUN go mod download

Expand All @@ -23,7 +26,7 @@ RUN go build -o voltaserve-conversion
FROM ubuntu:noble

RUN apt-get update &&\
apt-get install -y htop
apt-get install -y htop libvips

WORKDIR /app

Expand Down
1 change: 1 addition & 0 deletions conversion/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/go-playground/validator/v10 v10.22.0
github.com/gofiber/fiber/v2 v2.52.5
github.com/google/uuid v1.6.0
github.com/h2non/bimg v1.1.9
github.com/joho/godotenv v1.5.1
github.com/minio/minio-go/v7 v7.0.73
github.com/speps/go-hashids/v2 v2.0.1
Expand Down
2 changes: 2 additions & 0 deletions conversion/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yG
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/h2non/bimg v1.1.9 h1:WH20Nxko9l/HFm4kZCA3Phbgu2cbHvYzxwxn9YROEGg=
github.com/h2non/bimg v1.1.9/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
Expand Down
70 changes: 46 additions & 24 deletions conversion/processor/image_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"strconv"
"strings"

"github.com/h2non/bimg"

"github.com/kouprlabs/voltaserve/conversion/client/api_client"
"github.com/kouprlabs/voltaserve/conversion/config"
"github.com/kouprlabs/voltaserve/conversion/helper"
Expand Down Expand Up @@ -54,53 +56,73 @@ func (p *ImageProcessor) Thumbnail(inputPath string, outputPath string) (*bool,
}

func (p *ImageProcessor) MeasureImage(inputPath string) (*api_client.ImageProps, error) {
size, err := infra.NewCommand().ReadOutput("identify", "-format", "%w,%h", inputPath)
if err != nil {
return nil, err
}
values := strings.Split(*size, ",")
width, err := strconv.Atoi(helper.RemoveNonNumeric(values[0]))
b, err := bimg.Read(inputPath)
if err != nil {
return nil, err
}
height, err := strconv.Atoi(helper.RemoveNonNumeric(values[1]))
size, err := bimg.Size(b)
if err != nil {
return nil, err
}
return &api_client.ImageProps{
Width: width,
Height: height,
Width: size.Width,
Height: size.Height,
}, nil
}

func (p *ImageProcessor) ResizeImage(inputPath string, width int, height int, outputPath string) error {
var widthStr string
if width == 0 {
widthStr = ""
} else {
widthStr = strconv.FormatInt(int64(width), 10)
}
var heightStr string
if height == 0 {
heightStr = ""
} else {
heightStr = strconv.FormatInt(int64(height), 10)
}
if err := infra.NewCommand().Exec("convert", "-resize", widthStr+"x"+heightStr, inputPath, outputPath); err != nil {
b, err := bimg.Read(inputPath)
if err != nil {
return err
}
b, err = bimg.Resize(b, bimg.Options{
Width: width,
Height: height,
})
if err != nil {
return err
}
if err = bimg.Write(outputPath, b); err != nil {
return err
}
return nil
}

func (p *ImageProcessor) ConvertImage(inputPath string, outputPath string) error {
if err := infra.NewCommand().Exec("convert", inputPath, outputPath); err != nil {
b, err := bimg.Read(inputPath)
if err != nil {
return err
}
var imageType bimg.ImageType
if strings.HasSuffix(outputPath, ".png") {
imageType = bimg.PNG
} else if strings.HasSuffix(outputPath, ".jpg") {
imageType = bimg.JPEG
} else if strings.HasSuffix(outputPath, ".webp") {
imageType = bimg.WEBP
} else if strings.HasSuffix(outputPath, ".tiff") || strings.HasSuffix(outputPath, ".tif") {
imageType = bimg.TIFF
}
b, err = bimg.NewImage(b).Convert(imageType)
if err != nil {
return err
}
if err = bimg.Write(outputPath, b); err != nil {
return err
}
return nil
}

func (p *ImageProcessor) RemoveAlphaChannel(inputPath string, outputPath string) error {
if err := infra.NewCommand().Exec("convert", inputPath, "-alpha", "off", outputPath); err != nil {
b, err := bimg.Read(inputPath)
if err != nil {
return err
}
b, err = bimg.NewImage(b).Convert(bimg.JPEG)
if err != nil {
return err
}
if err = bimg.Write(outputPath, b); err != nil {
return err
}
return nil
Expand Down
12 changes: 9 additions & 3 deletions mosaic/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,25 @@
# by the GNU Affero General Public License v3.0 only, included in the file
# licenses/AGPL.txt.

FROM golang:1.22-alpine AS builder
FROM golang:1.22-bookworm AS builder

RUN apt-get update &&\
apt-get install -y libvips-dev

WORKDIR /build

COPY go.mod go.sum .
COPY go.mod go.sum ./

RUN go mod download

COPY . .

RUN go build -o voltaserve-mosaic

FROM golang:1.22-alpine AS runner
FROM golang:1.22-bookworm AS runner

RUN apt-get update &&\
apt-get install -y libvips

WORKDIR /app

Expand Down
76 changes: 43 additions & 33 deletions mosaic/builder/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ package builder

import (
"fmt"
"image"
"os"
"path/filepath"

"github.com/disintegration/imaging"
"github.com/h2non/bimg"
)

type Metadata struct {
Expand Down Expand Up @@ -177,18 +175,20 @@ type ZoomLevel struct {
}

type Image struct {
img image.Image
file string
buffer []byte
file string
width *int
height *int
}

func NewImage(file string) (*Image, error) {
img, err := imaging.Open(file)
b, err := bimg.Read(file)
if err != nil {
return nil, err
}
return &Image{
img: img,
file: file,
buffer: b,
file: file,
}, nil
}

Expand All @@ -197,53 +197,63 @@ func NewImageFromSource(source *Image) (*Image, error) {
return nil, fmt.Errorf("source image is nil")
}
return &Image{
img: source.img,
file: source.file,
buffer: source.buffer,
file: source.file,
}, nil
}

func (img *Image) Width() int {
return img.img.Bounds().Dx()
if img.width == nil {
size, err := bimg.Size(img.buffer)
if err != nil {
return -1
}
img.width = new(int)
*img.width = size.Width
}
return *img.width
}

func (img *Image) Height() int {
return img.img.Bounds().Dy()
if img.height == nil {
size, err := bimg.Size(img.buffer)
if err != nil {
return -1
}
img.height = new(int)
*img.height = size.Height
}
return *img.height
}

func (img *Image) Extension() string {
return filepath.Ext(img.file)
}

func (img *Image) Load(file string) error {
loadedImg, err := imaging.Open(file)
func (img *Image) Crop(x, y, width, height int) error {
var err error
img.buffer, err = bimg.NewImage(img.buffer).Extract(y, x, width, height)
if err != nil {
return err
}
img.img = loadedImg
img.file = file
return nil
}

func (img *Image) Crop(x, y, width, height int) error {
croppedImg := imaging.Crop(img.img, image.Rect(x, y, x+width, y+height))
img.img = croppedImg
return nil
}

func (img *Image) CropWithRectangle(rectangle Rectangle) error {
return img.Crop(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height)
}

func (img *Image) ScaleWithAspectRatio(width, height int) error {
scaledImg := imaging.Fit(img.img, width, height, imaging.Lanczos)
img.img = scaledImg
var err error
img.buffer, err = bimg.Resize(img.buffer, bimg.Options{
Width: width,
Height: height,
})
if err != nil {
return err
}
return nil
}

func (img *Image) Save(file string) error {
return imaging.Save(img.img, file)
}

func (img *Image) SaveAsPngToStream(stream *os.File) error {
return imaging.Encode(stream, img.img, imaging.PNG)
if err := bimg.Write(file, img.buffer); err != nil {
return err
}
return nil
}
14 changes: 7 additions & 7 deletions mosaic/builder/mosaic_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,19 +146,19 @@ func (mb *MosaicBuilder) Decompose(image *Image, zoomLevel int, region Region) Z

cols := 1
if tileWidthExceeded {
cols = int(image.Width()) / mb.TileSize().Width()
cols = image.Width() / mb.TileSize().Width()
}
rows := 1
if tileHeightExceeded {
rows = int(image.Height()) / mb.TileSize().Height()
rows = image.Height() / mb.TileSize().Height()
}
remainingWidth := 0
if tileWidthExceeded {
remainingWidth = int(image.Width()) - (mb.TileSize().Width() * cols)
remainingWidth = image.Width() - (mb.TileSize().Width() * cols)
}
remainingHeight := 0
if tileHeightExceeded {
remainingHeight = int(image.Height()) - (mb.TileSize().Height() * rows)
remainingHeight = image.Height() - (mb.TileSize().Height() * rows)
}
totalCols := cols
if remainingWidth != 0 {
Expand All @@ -171,10 +171,10 @@ func (mb *MosaicBuilder) Decompose(image *Image, zoomLevel int, region Region) Z

adaptedTileSize := *mb.TileSize()
if !tileWidthExceeded {
adaptedTileSize.SetWidth(int(image.Width()))
adaptedTileSize.SetWidth(image.Width())
}
if !tileHeightExceeded {
adaptedTileSize.SetHeight(int(image.Height()))
adaptedTileSize.SetHeight(image.Height())
}

colStart, colEnd, rowStart, rowEnd := 0, cols-1, 0, rows-1
Expand Down Expand Up @@ -303,7 +303,7 @@ func (mb *MosaicBuilder) Scale(zoomLevel int) (*Image, error) {
}

func (mb *MosaicBuilder) GetImageSizeForZoomLevel(zoomLevel int) Size {
size := Size{Width: int(mb.image.Width()), Height: int(mb.image.Height())}
size := Size{Width: mb.image.Width(), Height: mb.image.Height()}
counter := 0
for {
if counter == zoomLevel {
Expand Down
Loading