mirror of https://mirror.trustie.net/root/bimg.git
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.
|
||||
|
||||
|
|
48
image.go
48
image.go
|
@ -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,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
|
||||
}
|
||||
|
|
|
@ -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
77
vips.go
|
@ -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
2
vips.h
|
@ -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…
Reference in New Issue