This commit is contained in:
Tomas Aparicio 2015-04-07 23:02:31 +02:00
parent b93919d182
commit 16576f49c9
6 changed files with 170 additions and 52 deletions

View File

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

View File

@ -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}
}

View File

@ -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,17 +23,79 @@ 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 TestImageCropByWidth(t *testing.T) {
buf, err := initImage("test.jpg").CropByWidth(600)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
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) {
buf, err := initImage("test.jpg").Thumbnail(100)
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 {
@ -37,14 +104,6 @@ func TestImageFlip(t *testing.T) {
Write("fixtures/test_flip_out.jpg", buf)
}
func TestImageThumbnail(t *testing.T) {
buf, err := initImage("test.jpg").Thumbnail(100)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_thumbnail_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
}

View File

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

77
vips.go
View File

@ -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)
}

2
vips.h
View File

@ -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