Skip to content

Commit

Permalink
fix: hybrid integration of bild & ImageMagick (#269)
Browse files Browse the repository at this point in the history
* fix: hybrid integration of bild and ImageMagick

* chore: format code

* fix: add support for .tif extension

* fix: various issues

* fix: base mosaic on preview
  • Loading branch information
bouassaba authored Aug 13, 2024
1 parent 891c0a8 commit c7ab8df
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 60 deletions.
1 change: 1 addition & 0 deletions api/infra/file_identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func (fi *FileIdentifier) IsImage(path string) bool {
".gif",
".webp",
".tiff",
".tif",
".bmp",
".ico",
".heif",
Expand Down
6 changes: 3 additions & 3 deletions api/infra/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (mgr *S3Manager) GetObject(objectName string, bucketName string, opts minio
var buf bytes.Buffer
written, err := io.Copy(io.Writer(&buf), reader)
if err != nil {
return nil, nil, nil
return nil, nil, err
}
return &buf, &written, nil
}
Expand All @@ -113,7 +113,7 @@ func (mgr *S3Manager) GetObjectWithBuffer(objectName string, bucketName string,
}
written, err := io.Copy(io.Writer(buf), reader)
if err != nil {
return nil, nil
return nil, err
}
return &written, nil
}
Expand All @@ -129,7 +129,7 @@ func (mgr *S3Manager) GetText(objectName string, bucketName string, opts minio.G
buf := new(strings.Builder)
_, err = io.Copy(buf, reader)
if err != nil {
return "", nil
return "", err
}
return buf.String(), nil
}
Expand Down
12 changes: 6 additions & 6 deletions api/service/mosaic_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ func (svc *MosaicService) Create(id string, userID string) error {
PipelineID: helper.ToPtr(conversion_client.PipelineMosaic),
TaskID: task.GetID(),
SnapshotID: snapshot.GetID(),
Bucket: snapshot.GetOriginal().Bucket,
Key: snapshot.GetOriginal().Key,
Bucket: snapshot.GetPreview().Bucket,
Key: snapshot.GetPreview().Key,
}); err != nil {
return err
}
Expand Down Expand Up @@ -129,7 +129,7 @@ func (svc *MosaicService) Delete(id string, userID string) error {
if !snapshot.HasMosaic() {
return errorpkg.NewMosaicNotFoundError(nil)
}
if svc.fileIdent.IsImage(snapshot.GetOriginal().Key) {
if svc.fileIdent.IsImage(snapshot.GetPreview().Key) {
task, err := svc.taskSvc.insertAndSync(repo.TaskInsertOptions{
ID: helper.NewID(),
Name: "Deleting mosaic.",
Expand All @@ -149,7 +149,7 @@ func (svc *MosaicService) Delete(id string, userID string) error {
go func(task model.Task, snapshot model.Snapshot) {
err = svc.mosaicClient.Delete(mosaic_client.MosaicDeleteOptions{
S3Key: filepath.FromSlash(snapshot.GetID()),
S3Bucket: snapshot.GetOriginal().Bucket,
S3Bucket: snapshot.GetPreview().Bucket,
})
if err != nil {
value := err.Error()
Expand Down Expand Up @@ -213,7 +213,7 @@ func (svc *MosaicService) GetInfo(id string, userID string) (*MosaicInfo, error)
}
res, err := svc.mosaicClient.GetMetadata(mosaic_client.MosaicGetMetadataOptions{
S3Key: filepath.FromSlash(snapshot.GetID()),
S3Bucket: snapshot.GetOriginal().Bucket,
S3Bucket: snapshot.GetPreview().Bucket,
})
if err != nil {
return nil, err
Expand Down Expand Up @@ -261,7 +261,7 @@ func (svc *MosaicService) DownloadTileBuffer(id string, opts MosaicDownloadTileO
}
res, err := svc.mosaicClient.DownloadTileBuffer(mosaic_client.MosaicDownloadTileOptions{
S3Key: filepath.FromSlash(snapshot.GetID()),
S3Bucket: snapshot.GetOriginal().Bucket,
S3Bucket: snapshot.GetPreview().Bucket,
ZoomLevel: opts.ZoomLevel,
Row: opts.Row,
Col: opts.Col,
Expand Down
14 changes: 14 additions & 0 deletions conversion/helper/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package helper

func AspectRatio(width, height, originalWidth, originalHeight int) (int, int) {
if width == 0 && height == 0 {
return originalWidth, originalHeight
}
if width == 0 {
width = (height * originalWidth) / originalHeight
}
if height == 0 {
height = (width * originalHeight) / originalWidth
}
return width, height
}
2 changes: 2 additions & 0 deletions conversion/identifier/file_identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func (fi *FileIdentifier) IsImage(path string) bool {
".gif",
".webp",
".tiff",
".tif",
".bmp",
".ico",
".heif",
Expand All @@ -125,6 +126,7 @@ func (fi *FileIdentifier) IsNonAlphaChannelImage(path string) bool {
".jpeg",
".gif",
".tiff",
".tif",
".bmp",
}
extension := filepath.Ext(path)
Expand Down
32 changes: 32 additions & 0 deletions conversion/identifier/image_identifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package identifier

import (
"path/filepath"
"strings"
)

type ImageIdentifier struct{}

func NewImageIdentifier() *ImageIdentifier {
return &ImageIdentifier{}
}

func (ii ImageIdentifier) IsJPEG(path string) bool {
path = strings.ToLower(path)
return filepath.Ext(path) == ".jpg" ||
filepath.Ext(path) == ".jpeg" ||
filepath.Ext(path) == ".jpe" ||
filepath.Ext(path) == ".jfif" ||
filepath.Ext(path) == ".jif"
}

func (ii ImageIdentifier) IsPNG(path string) bool {
path = strings.ToLower(path)
return filepath.Ext(path) == ".png"
}

func (ii ImageIdentifier) IsTIFF(path string) bool {
path = strings.ToLower(path)
return filepath.Ext(path) == ".tiff" ||
filepath.Ext(path) == ".tif"
}
4 changes: 2 additions & 2 deletions conversion/infra/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (mgr *S3Manager) GetObject(objectName string, bucketName string, opts minio
var buf bytes.Buffer
_, err = io.Copy(io.Writer(&buf), reader)
if err != nil {
return nil, nil
return nil, err
}
return &buf, nil
}
Expand All @@ -146,7 +146,7 @@ func (mgr *S3Manager) GetText(objectName string, bucketName string, opts minio.G
buf := new(strings.Builder)
_, err = io.Copy(buf, reader)
if err != nil {
return "", nil
return "", err
}
return buf.String(), nil
}
Expand Down
6 changes: 4 additions & 2 deletions conversion/pipeline/image_pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type imagePipeline struct {
taskClient *api_client.TaskClient
snapshotClient *api_client.SnapshotClient
fileIdent *identifier.FileIdentifier
imageIdent *identifier.ImageIdentifier
config *config.Config
}

Expand All @@ -42,6 +43,7 @@ func NewImagePipeline() model.Pipeline {
taskClient: api_client.NewTaskClient(),
snapshotClient: api_client.NewSnapshotClient(),
fileIdent: identifier.NewFileIdentifier(),
imageIdent: identifier.NewImageIdentifier(),
config: config.GetConfig(),
}
}
Expand Down Expand Up @@ -69,7 +71,7 @@ func (p *imagePipeline) Run(opts api_client.PipelineRunOptions) error {
return err
}
var imagePath string
if filepath.Ext(inputPath) == ".tiff" {
if p.imageIdent.IsTIFF(inputPath) {
if err := p.taskClient.Patch(opts.TaskID, api_client.TaskPatchOptions{
Fields: []string{api_client.TaskFieldName},
Name: helper.ToPtr("Converting TIFF image to JPEG format."),
Expand Down Expand Up @@ -137,7 +139,7 @@ func (p *imagePipeline) measureImageDimensions(inputPath string, opts api_client
}

func (p *imagePipeline) createThumbnail(inputPath string, opts api_client.PipelineRunOptions) error {
tmpPath := filepath.FromSlash(os.TempDir() + "/" + helper.NewID() + ".png")
tmpPath := filepath.FromSlash(os.TempDir() + "/" + helper.NewID() + filepath.Ext(inputPath))
isAvailable, err := p.imageProc.Thumbnail(inputPath, tmpPath)
if err != nil {
return err
Expand Down
126 changes: 85 additions & 41 deletions conversion/processor/image_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
package processor

import (
"fmt"
"strconv"
"strings"

Expand All @@ -26,14 +25,16 @@ import (
)

type ImageProcessor struct {
fileIdent *identifier.FileIdentifier
config *config.Config
fileIdent *identifier.FileIdentifier
imageIdent *identifier.ImageIdentifier
config *config.Config
}

func NewImageProcessor() *ImageProcessor {
return &ImageProcessor{
fileIdent: identifier.NewFileIdentifier(),
config: config.GetConfig(),
fileIdent: identifier.NewFileIdentifier(),
imageIdent: identifier.NewImageIdentifier(),
config: config.GetConfig(),
}
}

Expand All @@ -44,11 +45,13 @@ func (p *ImageProcessor) Thumbnail(inputPath string, outputPath string) (*bool,
}
if props.Width > p.config.Limits.ImagePreviewMaxWidth || props.Height > p.config.Limits.ImagePreviewMaxHeight {
if props.Width > props.Height {
if err := p.ResizeImage(inputPath, p.config.Limits.ImagePreviewMaxWidth, 0, outputPath); err != nil {
newWidth, newHeight := helper.AspectRatio(p.config.Limits.ImagePreviewMaxWidth, 0, props.Width, props.Height)
if err := p.ResizeImage(inputPath, newWidth, newHeight, outputPath); err != nil {
return nil, err
}
} else {
if err := p.ResizeImage(inputPath, 0, p.config.Limits.ImagePreviewMaxHeight, outputPath); err != nil {
newWidth, newHeight := helper.AspectRatio(0, p.config.Limits.ImagePreviewMaxHeight, props.Width, props.Height)
if err := p.ResizeImage(inputPath, newWidth, newHeight, outputPath); err != nil {
return nil, err
}
}
Expand All @@ -58,55 +61,92 @@ func (p *ImageProcessor) Thumbnail(inputPath string, outputPath string) (*bool,
}

func (p *ImageProcessor) MeasureImage(inputPath string) (*api_client.ImageProps, error) {
img, err := imgio.Open(inputPath)
if err != nil {
return nil, err
bildImage, err := imgio.Open(inputPath)
if err == nil {
return &api_client.ImageProps{
Width: bildImage.Bounds().Dx(),
Height: bildImage.Bounds().Dy(),
}, nil
} else {
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]))
if err != nil {
return nil, err
}
height, err := strconv.Atoi(helper.RemoveNonNumeric(values[1]))
if err != nil {
return nil, err
}
return &api_client.ImageProps{
Width: width,
Height: height,
}, nil
}
return &api_client.ImageProps{
Width: img.Bounds().Dx(),
Height: img.Bounds().Dy(),
}, nil
}

func (p *ImageProcessor) ResizeImage(inputPath string, width int, height int, outputPath string) error {
img, err := imgio.Open(inputPath)
if err != nil {
return err
}
newImg := transform.Resize(img, width, height, transform.Lanczos)
var encoder imgio.Encoder
if strings.HasSuffix(inputPath, ".png") {
encoder = imgio.PNGEncoder()
} else if strings.HasSuffix(inputPath, ".jpg") {
encoder = imgio.JPEGEncoder(100)
bildImage, err := imgio.Open(inputPath)
if err == nil && p.canBeHandledByBild(outputPath) {
newImage := transform.Resize(bildImage, width, height, transform.Lanczos)
var encoder imgio.Encoder
if p.imageIdent.IsPNG(inputPath) {
encoder = imgio.PNGEncoder()
} else if p.imageIdent.IsJPEG(inputPath) {
encoder = imgio.JPEGEncoder(100)
}
return imgio.Save(outputPath, newImage, encoder)
} else {
return fmt.Errorf("unsupported image format: %s", inputPath)
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 {
return err
}
return nil
}
return imgio.Save(outputPath, newImg, encoder)
}

func (p *ImageProcessor) ConvertImage(inputPath string, outputPath string) error {
img, err := imgio.Open(inputPath)
if err != nil {
return err
}
var encoder imgio.Encoder
if strings.HasSuffix(outputPath, ".png") {
encoder = imgio.PNGEncoder()
} else if strings.HasSuffix(outputPath, ".jpg") {
encoder = imgio.JPEGEncoder(100)
bildImage, err := imgio.Open(inputPath)
if err == nil && p.canBeHandledByBild(outputPath) {
var encoder imgio.Encoder
if p.imageIdent.IsPNG(outputPath) {
encoder = imgio.PNGEncoder()
} else if p.imageIdent.IsJPEG(outputPath) {
encoder = imgio.JPEGEncoder(100)
}
return imgio.Save(outputPath, bildImage, encoder)
} else {
return fmt.Errorf("unsupported image format: %s", inputPath)
if err := infra.NewCommand().Exec("convert", inputPath, outputPath); err != nil {
return err
}
return nil
}
return imgio.Save(outputPath, img, encoder)
}

func (p *ImageProcessor) RemoveAlphaChannel(inputPath string, outputPath string) error {
img, err := imgio.Open(inputPath)
if err != nil {
return err
bildImage, err := imgio.Open(inputPath)
if err == nil && p.canBeHandledByBild(outputPath) {
return imgio.Save(outputPath, bildImage, imgio.JPEGEncoder(100))
} else {
if err := infra.NewCommand().Exec("convert", inputPath, "-alpha", "off", outputPath); err != nil {
return err
}
return nil
}
return imgio.Save(outputPath, img, imgio.JPEGEncoder(100))
}

func (p *ImageProcessor) DPIFromImage(inputPath string) (*int, error) {
Expand All @@ -128,3 +168,7 @@ func (p *ImageProcessor) DPIFromImage(inputPath string) (*int, error) {
}
return helper.ToPtr(int((xRes + yRes) / 2)), nil
}

func (p *ImageProcessor) canBeHandledByBild(path string) bool {
return p.imageIdent.IsJPEG(path) || p.imageIdent.IsPNG(path)
}
Loading

0 comments on commit c7ab8df

Please sign in to comment.