mirror of
https://github.com/talgo-cloud/bimg.git
synced 2026-03-14 09:55:56 -07:00
fix(#46): infer resize operation
This commit is contained in:
parent
4678a066b6
commit
9db6f2a0ea
3 changed files with 78 additions and 37 deletions
52
resize.go
52
resize.go
|
|
@ -23,17 +23,6 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults
|
|
||||||
if o.Quality == 0 {
|
|
||||||
o.Quality = QUALITY
|
|
||||||
}
|
|
||||||
if o.Compression == 0 {
|
|
||||||
o.Compression = 6
|
|
||||||
}
|
|
||||||
if o.Type == 0 {
|
|
||||||
o.Type = imageType
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsTypeSupported(o.Type) == false {
|
if IsTypeSupported(o.Type) == false {
|
||||||
return nil, errors.New("Unsupported image output type")
|
return nil, errors.New("Unsupported image output type")
|
||||||
}
|
}
|
||||||
|
|
@ -43,14 +32,20 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
inWidth := int(image.Xsize)
|
inWidth := int(image.Xsize)
|
||||||
inHeight := int(image.Ysize)
|
inHeight := int(image.Ysize)
|
||||||
|
|
||||||
|
// Define default options
|
||||||
|
applyDefaults(&o, imageType)
|
||||||
|
// Infer the required operation based on the in/out image sizes for a coherent transformation
|
||||||
|
normalizeOperation(&o, inWidth, inHeight)
|
||||||
|
|
||||||
// image calculations
|
// image calculations
|
||||||
factor := imageCalculations(&o, inWidth, inHeight)
|
factor := imageCalculations(&o, inWidth, inHeight)
|
||||||
shrink := calculateShrink(factor, o.Interpolator)
|
shrink := calculateShrink(factor, o.Interpolator)
|
||||||
residual := calculateResidual(factor, shrink)
|
residual := calculateResidual(factor, shrink)
|
||||||
|
|
||||||
// Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
// Do not enlarge the output if the input width or height
|
||||||
|
// are already less than the required dimensions
|
||||||
if o.Enlarge == false {
|
if o.Enlarge == false {
|
||||||
if inWidth < o.Width && inHeight < o.Height {
|
if inWidth < o.Width || inHeight < o.Height {
|
||||||
factor = 1.0
|
factor = 1.0
|
||||||
shrink = 1
|
shrink = 1
|
||||||
residual = 0
|
residual = 0
|
||||||
|
|
@ -88,7 +83,6 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
transformImage := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0
|
transformImage := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0
|
||||||
|
|
||||||
if transformImage {
|
if transformImage {
|
||||||
|
|
||||||
// Use vips_shrink with the integral reduction
|
// Use vips_shrink with the integral reduction
|
||||||
if shrink > 1 {
|
if shrink > 1 {
|
||||||
image, residual, err = shrinkImage(image, o, residual, shrink)
|
image, residual, err = shrinkImage(image, o, residual, shrink)
|
||||||
|
|
@ -138,6 +132,28 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyDefaults(o *Options, imageType ImageType) {
|
||||||
|
if o.Quality == 0 {
|
||||||
|
o.Quality = QUALITY
|
||||||
|
}
|
||||||
|
if o.Compression == 0 {
|
||||||
|
o.Compression = 6
|
||||||
|
}
|
||||||
|
if o.Type == 0 {
|
||||||
|
o.Type = imageType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeOperation(o *Options, inWidth, inHeight int) {
|
||||||
|
if o.Crop == false && o.Enlarge == false && o.Rotate == 0 && (o.Width > 0 || o.Height > 0) {
|
||||||
|
if inWidth > o.Width || inHeight > o.Height {
|
||||||
|
o.Crop = true
|
||||||
|
} else {
|
||||||
|
o.Enlarge = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) {
|
func extractImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, error) {
|
||||||
var err error = nil
|
var err error = nil
|
||||||
inWidth := int(image.Xsize)
|
inWidth := int(image.Xsize)
|
||||||
|
|
@ -239,7 +255,6 @@ func zoomImage(image *C.struct__VipsImage, zoom int) (*C.struct__VipsImage, erro
|
||||||
if zoom == 0 {
|
if zoom == 0 {
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return vipsZoom(image, zoom+1)
|
return vipsZoom(image, zoom+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,13 +265,12 @@ func affineImage(image *C.struct__VipsImage, o Options, residual float64) (*C.st
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Enlarge || (o.Width > int(newImage.Xsize) && o.Height > int(newImage.Ysize)) {
|
if o.Enlarge || o.Width > 0 || (o.Width > int(newImage.Xsize) && o.Height > int(newImage.Ysize)) {
|
||||||
C.g_object_unref(C.gpointer(image))
|
C.g_object_unref(C.gpointer(image))
|
||||||
image = newImage
|
return newImage, nil
|
||||||
} else {
|
|
||||||
C.g_object_unref(C.gpointer(newImage))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
C.g_object_unref(C.gpointer(newImage))
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,11 @@ func TestResize(t *testing.T) {
|
||||||
t.Fatal("Image is not jpeg")
|
t.Fatal("Image is not jpeg")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size, _ := Size(newImg)
|
||||||
|
if size.Height != options.Height || size.Width != options.Width {
|
||||||
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
|
|
||||||
Write("fixtures/test_out.jpg", newImg)
|
Write("fixtures/test_out.jpg", newImg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,25 +41,14 @@ func TestRotate(t *testing.T) {
|
||||||
t.Fatal("Image is not jpeg")
|
t.Fatal("Image is not jpeg")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size, _ := Size(newImg)
|
||||||
|
if size.Height != options.Width {
|
||||||
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
|
|
||||||
Write("fixtures/test_rotate_out.jpg", newImg)
|
Write("fixtures/test_rotate_out.jpg", newImg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCorruptedImage(t *testing.T) {
|
|
||||||
options := Options{Width: 800, Height: 600}
|
|
||||||
buf, _ := Read("fixtures/corrupt.jpg")
|
|
||||||
|
|
||||||
newImg, err := Resize(buf, options)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if DetermineImageType(newImg) != JPEG {
|
|
||||||
t.Fatal("Image is not jpeg")
|
|
||||||
}
|
|
||||||
|
|
||||||
Write("fixtures/test_corrupt_out.jpg", newImg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInvalidRotate(t *testing.T) {
|
func TestInvalidRotate(t *testing.T) {
|
||||||
options := Options{Width: 800, Height: 600, Rotate: 111}
|
options := Options{Width: 800, Height: 600, Rotate: 111}
|
||||||
buf, _ := Read("fixtures/test.jpg")
|
buf, _ := Read("fixtures/test.jpg")
|
||||||
|
|
@ -68,9 +62,35 @@ func TestInvalidRotate(t *testing.T) {
|
||||||
t.Fatal("Image is not jpeg")
|
t.Fatal("Image is not jpeg")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size, _ := Size(newImg)
|
||||||
|
if size.Height != options.Width {
|
||||||
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
|
|
||||||
Write("fixtures/test_invalid_rotate_out.jpg", newImg)
|
Write("fixtures/test_invalid_rotate_out.jpg", newImg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCorruptedImage(t *testing.T) {
|
||||||
|
options := Options{Width: 800, Height: 600}
|
||||||
|
buf, _ := Read("fixtures/corrupt.jpg")
|
||||||
|
|
||||||
|
newImg, err := Resize(buf, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if DetermineImageType(newImg) != JPEG {
|
||||||
|
t.Fatal("Image is not jpeg")
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _ := Size(newImg)
|
||||||
|
if size.Height != options.Height || size.Width != options.Width {
|
||||||
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write("fixtures/test_corrupt_out.jpg", newImg)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNoColorProfile(t *testing.T) {
|
func TestNoColorProfile(t *testing.T) {
|
||||||
options := Options{Width: 800, Height: 600, NoProfile: true}
|
options := Options{Width: 800, Height: 600, NoProfile: true}
|
||||||
buf, _ := Read("fixtures/test.jpg")
|
buf, _ := Read("fixtures/test.jpg")
|
||||||
|
|
@ -84,6 +104,11 @@ func TestNoColorProfile(t *testing.T) {
|
||||||
if metadata.Profile == true {
|
if metadata.Profile == true {
|
||||||
t.Fatal("Invalid profile data")
|
t.Fatal("Invalid profile data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size, _ := Size(newImg)
|
||||||
|
if size.Height != options.Height || size.Width != options.Width {
|
||||||
|
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConvert(t *testing.T) {
|
func TestConvert(t *testing.T) {
|
||||||
|
|
|
||||||
6
vips.go
6
vips.go
|
|
@ -78,11 +78,12 @@ func Initialize() {
|
||||||
C.vips_cache_set_max_mem(maxCacheMem)
|
C.vips_cache_set_max_mem(maxCacheMem)
|
||||||
C.vips_cache_set_max(maxCacheSize)
|
C.vips_cache_set_max(maxCacheSize)
|
||||||
|
|
||||||
// Define a custom libvips thread concurrency limit (this may generate thread-unsafe issues)
|
// Define a custom thread concurrency limit in libvips (this may generate thread-unsafe issues)
|
||||||
// See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414
|
// See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414
|
||||||
if os.Getenv("VIPS_CONCURRENCY") == "" {
|
if os.Getenv("VIPS_CONCURRENCY") == "" {
|
||||||
C.vips_concurrency_set(1)
|
C.vips_concurrency_set(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable libvips cache tracing
|
// Enable libvips cache tracing
|
||||||
if os.Getenv("VIPS_TRACE") != "" {
|
if os.Getenv("VIPS_TRACE") != "" {
|
||||||
C.vips_enable_cache_set_trace()
|
C.vips_enable_cache_set_trace()
|
||||||
|
|
@ -91,7 +92,8 @@ func Initialize() {
|
||||||
initialized = true
|
initialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-safe function to shutdown libvips. You could call this to drop caches as well.
|
// Thread-safe function to shutdown libvips.
|
||||||
|
// You can call this to drop caches as well.
|
||||||
// If libvips was already initialized, the function is no-op
|
// If libvips was already initialized, the function is no-op
|
||||||
func Shutdown() {
|
func Shutdown() {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue