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)
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.

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

@ -10,6 +10,11 @@ func TestImageResize(t *testing.T) {
if err != nil {
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)
}
@ -18,23 +23,64 @@ func TestImageExtract(t *testing.T) {
if err != nil {
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)
}
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) {
buf, err := initImage("test.jpg").Crop(800, 600)
if err != nil {
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)
}
func TestImageFlip(t *testing.T) {
buf, err := initImage("test.jpg").Flip()
func TestImageCropByWidth(t *testing.T) {
buf, err := initImage("test.jpg").CropByWidth(600)
if err != nil {
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) {
@ -42,9 +88,22 @@ func TestImageThumbnail(t *testing.T) {
if err != nil {
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)
}
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) {
buf, err := initImage("test_flip_out.jpg").Rotate(90)
if err != nil {
@ -81,3 +140,14 @@ func initImage(file string) *Image {
buf, _ := Read(path.Join("fixtures", file))
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)
// image calculations
factor := imageCalculations(o, inWidth, inHeight)
factor := imageCalculations(&o, inWidth, inHeight)
shrink := int(math.Max(math.Floor(factor), 1))
residual := float64(shrink) / factor
// Do not enlarge the output if the input width *or* height are already less than the required dimensions
if o.Enlarge == false {
if inWidth < o.Width && inHeight < o.Height {
factor = 1
factor = 1.0
shrink = 1
residual = 0
o.Width = inWidth
@ -169,7 +169,6 @@ func rotateImage(image *C.struct__VipsImage, o Options) (*C.struct__VipsImage, e
if o.Rotate > 0 {
image, err = vipsRotate(image, getAngle(o.Rotate))
}
if o.Flip > 0 {
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
}
func imageCalculations(o Options, inWidth, inHeight int) float64 {
func imageCalculations(o *Options, inWidth, inHeight int) float64 {
factor := 1.0
xfactor := float64(inWidth) / float64(o.Width)
yfactor := float64(inHeight) / float64(o.Height)

@ -8,7 +8,6 @@ import "C"
import (
"errors"
//"fmt"
"runtime"
"strings"
"sync"
@ -20,44 +19,51 @@ var (
initialized bool = false
)
type vipsImage C.struct__VipsImage
func init() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
panic("unsupported old vips version")
panic("unsupported old vips version!")
}
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() {
m.Lock()
runtime.LockOSThread()
defer m.Unlock()
defer runtime.UnlockOSThread()
err := C.vips_init(C.CString("bimg"))
if err != 0 {
Shutdown()
panic("unable to start vips!")
}
m.Lock()
defer m.Unlock()
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
initialized = true
}
// Explicit thread-safe libvips shutdown. Call this to drop caches.
// If libvips was already initialized, the function is no-op
func Shutdown() {
m.Lock()
defer m.Unlock()
if initialized == true {
m.Lock()
defer m.Unlock()
C.vips_shutdown()
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) {
var out *C.struct__VipsImage
defer C.g_object_unref(C.gpointer(image))
@ -197,26 +203,6 @@ func vipsImageType(buf []byte) 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 {
Quality int
Compression int
@ -255,9 +241,30 @@ func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) {
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 {
s := C.GoString(C.vips_error_buffer())
C.vips_error_clear()
C.vips_thread_shutdown()
// clean image memory buffer?
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);
} else if (imageType == TIFF) {
code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
#if (VIPS_MAJOR_VERSION >= 8)
} else if (imageType == MAGICK) {
code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
#endif
}
// Listen for "postclose" signal to delete input buffer

Loading…
Cancel
Save