master
Tomas Aparicio 11 years ago
parent b93919d182
commit 16576f49c9

@ -6,7 +6,7 @@ bimg is designed to be a small and efficient library with a generic and useful s
It uses internally libvips, which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use) It uses internally libvips, which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use)
and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images.
It can read JPEG, PNG, WEBP, TIFF and Magick formats and it can output to JPEG, PNG and WEBP. It supports common [image transformation](#supported-image-operations) operations such as crop, resize, rotate... and conversion between multiple formats. It can read JPEG, PNG, WEBP and TIFF formats and output to JPEG, PNG and WEBP. It supports common [image transformation](#supported-image-operations) operations such as crop, resize, rotate... and conversion between multiple formats.
For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation. For getting started, take a look to the [examples](#examples) and [programmatic API](https://godoc.org/github.com/h2non/bimg) documentation.

@ -4,6 +4,7 @@ type Image struct {
buffer []byte buffer []byte
} }
// Resize the image to fixed width and height
func (i *Image) Resize(width, height int) ([]byte, error) { func (i *Image) Resize(width, height int) ([]byte, error) {
options := Options{ options := Options{
Width: width, Width: width,
@ -12,6 +13,7 @@ func (i *Image) Resize(width, height int) ([]byte, error) {
return i.Process(options) return i.Process(options)
} }
// Extract area from the by X/Y axis
func (i *Image) Extract(top, left, width, height int) ([]byte, error) { func (i *Image) Extract(top, left, width, height int) ([]byte, error) {
options := Options{ options := Options{
Top: top, Top: top,
@ -22,6 +24,17 @@ func (i *Image) Extract(top, left, width, height int) ([]byte, error) {
return i.Process(options) return i.Process(options)
} }
// Enlarge the image from the by X/Y axis
func (i *Image) Enlarge(width, height int) ([]byte, error) {
options := Options{
Width: width,
Height: height,
Enlarge: true,
}
return i.Process(options)
}
// Crop an image by width and height
func (i *Image) Crop(width, height int) ([]byte, error) { func (i *Image) Crop(width, height int) ([]byte, error) {
options := Options{ options := Options{
Width: width, Width: width,
@ -31,6 +44,25 @@ func (i *Image) Crop(width, height int) ([]byte, error) {
return i.Process(options) return i.Process(options)
} }
// Crop an image by width (auto height)
func (i *Image) CropByWidth(width int) ([]byte, error) {
options := Options{
Width: width,
Crop: true,
}
return i.Process(options)
}
// Crop an image by height (auto width)
func (i *Image) CropByHeight(height int) ([]byte, error) {
options := Options{
Height: height,
Crop: true,
}
return i.Process(options)
}
// Thumbnail the image by the a given width by aspect ratio 4:4
func (i *Image) Thumbnail(pixels int) ([]byte, error) { func (i *Image) Thumbnail(pixels int) ([]byte, error) {
options := Options{ options := Options{
Width: pixels, Width: pixels,
@ -41,21 +73,25 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) {
return i.Process(options) return i.Process(options)
} }
// Rotate the image by given angle degrees (0, 90, 180 or 270)
func (i *Image) Rotate(a Angle) ([]byte, error) { func (i *Image) Rotate(a Angle) ([]byte, error) {
options := Options{Rotate: a} options := Options{Rotate: a}
return i.Process(options) return i.Process(options)
} }
// Flip the image about the vertical Y axis
func (i *Image) Flip() ([]byte, error) { func (i *Image) Flip() ([]byte, error) {
options := Options{Flip: VERTICAL} options := Options{Flip: VERTICAL}
return i.Process(options) return i.Process(options)
} }
// Flop the image about the horizontal X axis
func (i *Image) Convert(t ImageType) ([]byte, error) { func (i *Image) Convert(t ImageType) ([]byte, error) {
options := Options{Type: t} options := Options{Type: t}
return i.Process(options) return i.Process(options)
} }
// Transform the image by custom options
func (i *Image) Process(o Options) ([]byte, error) { func (i *Image) Process(o Options) ([]byte, error) {
image, err := Resize(i.buffer, o) image, err := Resize(i.buffer, o)
if err != nil { if err != nil {
@ -65,18 +101,22 @@ func (i *Image) Process(o Options) ([]byte, error) {
return image, nil return image, nil
} }
func (i *Image) Type() string { // Get image metadata (size, alpha channel, profile, EXIF rotation)
return DetermineImageTypeName(i.buffer)
}
func (i *Image) Metadata() (ImageMetadata, error) { func (i *Image) Metadata() (ImageMetadata, error) {
return Metadata(i.buffer) return Metadata(i.buffer)
} }
// Get image type format (jpeg, png, webp, tiff)
func (i *Image) Type() string {
return DetermineImageTypeName(i.buffer)
}
// Get image size
func (i *Image) Size() (ImageSize, error) { func (i *Image) Size() (ImageSize, error) {
return Size(i.buffer) return Size(i.buffer)
} }
// Creates a new image
func NewImage(buf []byte) *Image { func NewImage(buf []byte) *Image {
return &Image{buf} return &Image{buf}
} }

@ -10,6 +10,11 @@ func TestImageResize(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Cannot process the image: %#v", err) t.Errorf("Cannot process the image: %#v", err)
} }
if assertSize(buf, 300, 240) {
t.Error("Invalid image size")
}
Write("fixtures/test_resize_out.jpg", buf) Write("fixtures/test_resize_out.jpg", buf)
} }
@ -18,23 +23,64 @@ func TestImageExtract(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Cannot process the image: %#v", err) t.Errorf("Cannot process the image: %#v", err)
} }
if assertSize(buf, 300, 300) {
t.Error("Invalid image size")
}
Write("fixtures/test_extract_out.jpg", buf) Write("fixtures/test_extract_out.jpg", buf)
} }
func TestImageEnlarge(t *testing.T) {
buf, err := initImage("test.png").Enlarge(500, 380)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
if assertSize(buf, 500, 380) {
t.Error("Invalid image size")
}
Write("fixtures/test_enlarge_out.jpg", buf)
}
func TestImageCrop(t *testing.T) { func TestImageCrop(t *testing.T) {
buf, err := initImage("test.jpg").Crop(800, 600) buf, err := initImage("test.jpg").Crop(800, 600)
if err != nil { if err != nil {
t.Errorf("Cannot process the image: %#v", err) t.Errorf("Cannot process the image: %#v", err)
} }
if assertSize(buf, 800, 600) {
t.Error("Invalid image size")
}
Write("fixtures/test_crop_out.jpg", buf) Write("fixtures/test_crop_out.jpg", buf)
} }
func TestImageFlip(t *testing.T) { func TestImageCropByWidth(t *testing.T) {
buf, err := initImage("test.jpg").Flip() buf, err := initImage("test.jpg").CropByWidth(600)
if err != nil { if err != nil {
t.Errorf("Cannot process the image: %#v", err) t.Errorf("Cannot process the image: %#v", err)
} }
Write("fixtures/test_flip_out.jpg", buf)
if assertSize(buf, 600, 375) {
t.Error("Invalid image size")
}
Write("fixtures/test_crop_width_out.jpg", buf)
}
func TestImageCropByHeight(t *testing.T) {
buf, err := initImage("test.jpg").CropByHeight(300)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
if assertSize(buf, 800, 480) {
t.Error("Invalid image size")
}
Write("fixtures/test_crop_height_out.jpg", buf)
} }
func TestImageThumbnail(t *testing.T) { func TestImageThumbnail(t *testing.T) {
@ -42,9 +88,22 @@ func TestImageThumbnail(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Cannot process the image: %#v", err) t.Errorf("Cannot process the image: %#v", err)
} }
if assertSize(buf, 100, 100) {
t.Error("Invalid image size")
}
Write("fixtures/test_thumbnail_out.jpg", buf) Write("fixtures/test_thumbnail_out.jpg", buf)
} }
func TestImageFlip(t *testing.T) {
buf, err := initImage("test.jpg").Flip()
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_flip_out.jpg", buf)
}
func TestImageRotate(t *testing.T) { func TestImageRotate(t *testing.T) {
buf, err := initImage("test_flip_out.jpg").Rotate(90) buf, err := initImage("test_flip_out.jpg").Rotate(90)
if err != nil { if err != nil {
@ -81,3 +140,14 @@ func initImage(file string) *Image {
buf, _ := Read(path.Join("fixtures", file)) buf, _ := Read(path.Join("fixtures", file))
return NewImage(buf) return NewImage(buf)
} }
func assertSize(buf []byte, width, height int) bool {
size, err := NewImage(buf).Size()
if err != nil {
return false
}
if size.Width != 220 || size.Height != 300 {
return false
}
return true
}

@ -45,14 +45,14 @@ func Resize(buf []byte, o Options) ([]byte, error) {
inHeight := int(image.Ysize) inHeight := int(image.Ysize)
// image calculations // image calculations
factor := imageCalculations(o, inWidth, inHeight) factor := imageCalculations(&o, inWidth, inHeight)
shrink := int(math.Max(math.Floor(factor), 1)) shrink := int(math.Max(math.Floor(factor), 1))
residual := float64(shrink) / factor residual := float64(shrink) / factor
// 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 factor = 1.0
shrink = 1 shrink = 1
residual = 0 residual = 0
o.Width = inWidth o.Width = inWidth
@ -169,7 +169,6 @@ func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, e
if o.Rotate > 0 { if o.Rotate > 0 {
image, err = vipsRotate(image, getAngle(o.Rotate)) image, err = vipsRotate(image, getAngle(o.Rotate))
} }
if o.Flip > 0 { if o.Flip > 0 {
image, err = vipsFlip(image, o.Flip) image, err = vipsFlip(image, o.Flip)
} }
@ -223,7 +222,7 @@ func shrinkJpegImage(buf []byte, input *C.struct__VipsImage, factor float64, shr
return image, factor, err return image, factor, err
} }
func imageCalculations(o Options, inWidth, inHeight int) float64 { func imageCalculations(o *Options, inWidth, inHeight int) float64 {
factor := 1.0 factor := 1.0
xfactor := float64(inWidth) / float64(o.Width) xfactor := float64(inWidth) / float64(o.Width)
yfactor := float64(inHeight) / float64(o.Height) yfactor := float64(inHeight) / float64(o.Height)

@ -8,7 +8,6 @@ import "C"
import ( import (
"errors" "errors"
//"fmt"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
@ -20,44 +19,51 @@ var (
initialized bool = false initialized bool = false
) )
type vipsImage C.struct__VipsImage
func init() { func init() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 { if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
panic("unsupported old vips version") panic("unsupported old vips version!")
} }
Initialize() Initialize()
C.vips_concurrency_set(0) // default
C.vips_cache_set_max_mem(100 * 1024 * 1024) // 100 MB
C.vips_cache_set_max(500) // 500 operations
} }
// Explicit thread-safe start of libvips.
// You should only call this function if you previously shutdown libvips
func Initialize() { func Initialize() {
m.Lock()
runtime.LockOSThread()
defer m.Unlock()
defer runtime.UnlockOSThread()
err := C.vips_init(C.CString("bimg")) err := C.vips_init(C.CString("bimg"))
if err != 0 { if err != 0 {
Shutdown() Shutdown()
panic("unable to start vips!") panic("unable to start vips!")
} }
m.Lock() C.vips_concurrency_set(0) // default
defer m.Unlock() C.vips_cache_set_max_mem(100 * 1024 * 1024) // 100 MB
C.vips_cache_set_max(500) // 500 operations
initialized = true initialized = true
} }
// Explicit thread-safe libvips shutdown. Call this to drop caches.
// If libvips was already initialized, the function is no-op
func Shutdown() { func Shutdown() {
m.Lock()
defer m.Unlock()
if initialized == true { if initialized == true {
m.Lock()
defer m.Unlock()
C.vips_shutdown() C.vips_shutdown()
initialized = false initialized = false
} }
} }
// Output to stdout collected data for debugging purposes
func VipsDebug() {
C.im__print_all()
}
func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) { func vipsRotate(image *C.struct__VipsImage, angle Angle) (*C.struct__VipsImage, error) {
var out *C.struct__VipsImage var out *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(image)) defer C.g_object_unref(C.gpointer(image))
@ -197,26 +203,6 @@ func vipsImageType(buf []byte) ImageType {
return imageType return imageType
} }
func vipsExifOrientation(image *C.struct__VipsImage) int {
return int(C.vips_exif_orientation(image))
}
func vipsHasAlpha(image *C.struct__VipsImage) bool {
return int(C.has_alpha_channel(image)) > 0
}
func vipsHasProfile(image *C.struct__VipsImage) bool {
return int(C.has_profile_embed(image)) > 0
}
func vipsWindowSize(name string) float64 {
return float64(C.interpolator_window_size(C.CString(name)))
}
func vipsSpace(image *C.struct__VipsImage) string {
return C.GoString(C.vips_enum_nick_bridge(image))
}
type vipsSaveOptions struct { type vipsSaveOptions struct {
Quality int Quality int
Compression int Compression int
@ -255,9 +241,30 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) {
return buf, nil return buf, nil
} }
func vipsExifOrientation(image *C.struct__VipsImage) int {
return int(C.vips_exif_orientation(image))
}
func vipsHasAlpha(image *C.struct__VipsImage) bool {
return int(C.has_alpha_channel(image)) > 0
}
func vipsHasProfile(image *C.struct__VipsImage) bool {
return int(C.has_profile_embed(image)) > 0
}
func vipsWindowSize(name string) float64 {
return float64(C.interpolator_window_size(C.CString(name)))
}
func vipsSpace(image *C.struct__VipsImage) string {
return C.GoString(C.vips_enum_nick_bridge(image))
}
func catchVipsError() error { func catchVipsError() error {
s := C.GoString(C.vips_error_buffer()) s := C.GoString(C.vips_error_buffer())
C.vips_error_clear() C.vips_error_clear()
C.vips_thread_shutdown() C.vips_thread_shutdown()
// clean image memory buffer?
return errors.New(s) return errors.New(s)
} }

@ -151,8 +151,10 @@ vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) {
code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
} else if (imageType == TIFF) { } else if (imageType == TIFF) {
code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
#if (VIPS_MAJOR_VERSION >= 8)
} else if (imageType == MAGICK) { } else if (imageType == MAGICK) {
code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
#endif
} }
// Listen for "postclose" signal to delete input buffer // Listen for "postclose" signal to delete input buffer

Loading…
Cancel
Save