// Metadata returns the image metadata (size, type, alpha channel, profile, EXIF orientation...). func Metadata(buf []byte) (ImageMetadata, error) { defer C.vips_thread_shutdown() image, imageType, err := vipsRead(buf) if err != nil { return ImageMetadata{}, err } defer C.g_object_unref(C.gpointer(image)) size := ImageSize{ Width: int(image.Xsize), Height: int(image.Ysize), } metadata := ImageMetadata{ Size: size, Channels: int(image.Bands), Orientation: vipsExifOrientation(image), Alpha: vipsHasAlpha(image), Profile: vipsHasProfile(image), Space: vipsSpace(image), Type: ImageTypeName(imageType), } return metadata, nil }
func Resize(buf []byte, o Options) ([]byte, error) { debug("%#+v", o) // detect (if possible) the file type typ := UNKNOWN switch { case bytes.Equal(buf[:2], MARKER_JPEG): typ = JPEG case bytes.Equal(buf[:2], MARKER_PNG): typ = PNG default: return nil, errors.New("unknown image format") } // create an image instance var image, tmpImage *C.struct__VipsImage // feed it switch typ { case JPEG: C.vips_jpegload_buffer_seq(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image) case PNG: C.vips_pngload_buffer_seq(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image) } // cleanup defer func() { C.vips_thread_shutdown() C.vips_error_clear() }() // defaults if o.Quality == 0 { o.Quality = 100 } // get WxH inWidth := int(image.Xsize) inHeight := int(image.Ysize) // prepare for factor factor := 0.0 // image calculations switch { // Fixed width and height case o.Width > 0 && o.Height > 0: xf := float64(inWidth) / float64(o.Width) yf := float64(inHeight) / float64(o.Height) if o.Crop { factor = math.Min(xf, yf) } else { factor = math.Max(xf, yf) } // Fixed width, auto height case o.Width > 0: factor = float64(inWidth) / float64(o.Width) o.Height = int(math.Floor(float64(inHeight) / factor)) // Fixed height, auto width case o.Height > 0: factor = float64(inHeight) / float64(o.Height) o.Width = int(math.Floor(float64(inWidth) / factor)) // Identity transform default: factor = 1 o.Width = inWidth o.Height = inHeight } debug("transform from %dx%d to %dx%d", inWidth, inHeight, o.Width, o.Height) // shrink shrink := int(math.Floor(factor)) if shrink < 1 { shrink = 1 } // residual 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 { if inWidth < o.Width && inHeight < o.Height { factor = 1 shrink = 1 residual = 0 o.Width = inWidth o.Height = inHeight } } debug("factor: %v, shrink: %v, residual: %v", factor, shrink, residual) // Try to use libjpeg shrink-on-load shrinkOnLoad := 1 if typ == JPEG && shrink >= 2 { switch { case shrink >= 8: factor = factor / 8 shrinkOnLoad = 8 case shrink >= 4: factor = factor / 4 shrinkOnLoad = 4 case shrink >= 2: factor = factor / 2 shrinkOnLoad = 2 } } if shrinkOnLoad > 1 { debug("shrink on load %d", shrinkOnLoad) // Recalculate integral shrink and double residual factor = math.Max(factor, 1.0) shrink = int(math.Floor(factor)) residual = float64(shrink) / factor // Reload input using shrink-on-load err := C.vips_jpegload_buffer_shrink(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &tmpImage, C.int(shrinkOnLoad)) C.g_object_unref(C.gpointer(image)) image = tmpImage if err != 0 { return nil, resizeError() } } if shrink > 1 { debug("shrink %d", shrink) // Use vips_shrink with the integral reduction err := C.vips_shrink_0(image, &tmpImage, C.double(float64(shrink)), C.double(float64(shrink))) C.g_object_unref(C.gpointer(image)) image = tmpImage if err != 0 { return nil, resizeError() } // Recalculate residual float based on dimensions of required vs shrunk images shrunkWidth := int(image.Xsize) shrunkHeight := int(image.Ysize) residualx := float64(o.Width) / float64(shrunkWidth) residualy := float64(o.Height) / float64(shrunkHeight) if o.Crop { residual = math.Max(residualx, residualy) } else { residual = math.Min(residualx, residualy) } } // Use vips_affine with the remaining float part debug("residual: %v", residual) if residual != 0 { debug("residual %.2f", residual) // Create interpolator - "bilinear" (default), "bicubic" or "nohalo" is := C.CString(o.Interpolator.String()) interpolator := C.vips_interpolate_new(is) // Perform affine transformation err := C.vips_affine_interpolator(image, &tmpImage, C.double(residual), 0, 0, C.double(residual), interpolator) C.g_object_unref(C.gpointer(image)) image = tmpImage C.free(unsafe.Pointer(is)) C.g_object_unref(C.gpointer(interpolator)) if err != 0 { return nil, resizeError() } } // Crop/embed affinedWidth := int(image.Xsize) affinedHeight := int(image.Ysize) if affinedWidth != o.Width || affinedHeight != o.Height { if o.Crop { // Crop debug("cropping") left, top := sharpCalcCrop(affinedWidth, affinedHeight, o.Width, o.Height, o.Gravity) o.Width = int(math.Min(float64(affinedWidth), float64(o.Width))) o.Height = int(math.Min(float64(affinedHeight), float64(o.Height))) err := C.vips_extract_area_0(image, &tmpImage, C.int(left), C.int(top), C.int(o.Width), C.int(o.Height)) C.g_object_unref(C.gpointer(image)) image = tmpImage if err != 0 { return nil, resizeError() } } else if o.Embed { debug("embedding with extend %d", o.Extend) left := (o.Width - affinedWidth) / 2 top := (o.Height - affinedHeight) / 2 err := C.vips_embed_extend(image, &tmpImage, C.int(left), C.int(top), C.int(o.Width), C.int(o.Height), C.int(o.Extend)) C.g_object_unref(C.gpointer(image)) image = tmpImage if err != 0 { return nil, resizeError() } } } else { debug("canvased same as affined") } // Always convert to sRGB colour space C.vips_colourspace_0(image, &tmpImage, C.VIPS_INTERPRETATION_sRGB) C.g_object_unref(C.gpointer(image)) image = tmpImage // Finally save length := C.size_t(0) var ptr unsafe.Pointer C.vips_jpegsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0) C.g_object_unref(C.gpointer(image)) // get back the buffer buf = C.GoBytes(ptr, C.int(length)) C.g_free(C.gpointer(ptr)) return buf, nil }
func catchVipsError() error { s := C.GoString(C.vips_error_buffer()) C.vips_error_clear() C.vips_thread_shutdown() return errors.New(s) }
func Resize(buf []byte, o Options) ([]byte, error) { defer C.vips_thread_shutdown() if len(buf) == 0 { return nil, errors.New("Image buffer is empty") } image, imageType, err := vipsRead(buf) if err != nil { 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 { return nil, errors.New("Unsupported image output type") } debug("Options: %#v", o) inWidth := int(image.Xsize) inHeight := int(image.Ysize) // image calculations factor := imageCalculations(&o, inWidth, inHeight) shrink := calculateShrink(factor, o.Interpolator) residual := calculateResidual(factor, shrink) // 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.0 shrink = 1 residual = 0 o.Width = inWidth o.Height = inHeight } } // Try to use libjpeg shrink-on-load if imageType == JPEG && shrink >= 2 { tmpImage, factor, err := shrinkJpegImage(buf, image, factor, shrink) if err != nil { return nil, err } image = tmpImage factor = math.Max(factor, 1.0) shrink = int(math.Floor(factor)) residual = float64(shrink) / factor } // Zoom image, if necessary image, err = zoomImage(image, o.Zoom) if err != nil { return nil, err } // Rotate / flip image, if necessary image, err = rotateAndFlipImage(image, o) if err != nil { return nil, err } // Transform image, if necessary transformImage := o.Width != inWidth || o.Height != inHeight || o.AreaWidth > 0 || o.AreaHeight > 0 if transformImage { // Use vips_shrink with the integral reduction if shrink > 1 { image, residual, err = shrinkImage(image, o, residual, shrink) if err != nil { return nil, err } } debug("Transform: factor=%v, shrink=%v, residual=%v, interpolator=%v", factor, shrink, residual, o.Interpolator.String()) // Affine with the remaining float part if residual != 0 { image, err = affineImage(image, o, residual) if err != nil { return nil, err } } // Extract area from image image, err = extractImage(image, o) if err != nil { return nil, err } } // Add watermark, if necessary image, err = watermakImage(image, o.Watermark) if err != nil { return nil, err } saveOptions := vipsSaveOptions{ Quality: o.Quality, Type: o.Type, Compression: o.Compression, Interlace: o.Interlace, NoProfile: o.NoProfile, } // Finally save as buffer buf, err = vipsSave(image, saveOptions) if err != nil { return nil, err } return buf, nil }
func Resize(buf []byte, o Options) ([]byte, error) { defer C.vips_thread_shutdown() if len(buf) == 0 { return nil, errors.New("Image buffer is empty") } image, imageType, err := vipsRead(buf) if err != nil { return nil, err } // Define default options applyDefaults(&o, imageType) if IsTypeSupported(o.Type) == false { return nil, errors.New("Unsupported image output type") } debug("Options: %#v", o) inWidth := int(image.Xsize) inHeight := int(image.Ysize) // Infer the required operation based on the in/out image sizes for a coherent transformation normalizeOperation(&o, inWidth, inHeight) // image calculations factor := imageCalculations(&o, inWidth, inHeight) shrink := calculateShrink(factor, o.Interpolator) residual := calculateResidual(factor, shrink) // Do not enlarge the output if the input width or height // are already less than the required dimensions if !o.Enlarge && !o.Force { if inWidth < o.Width && inHeight < o.Height { factor = 1.0 shrink = 1 residual = 0 o.Width = inWidth o.Height = inHeight } } // Try to use libjpeg shrink-on-load if imageType == JPEG && shrink >= 2 { tmpImage, factor, err := shrinkJpegImage(buf, image, factor, shrink) if err != nil { return nil, err } image = tmpImage factor = math.Max(factor, 1.0) shrink = int(math.Floor(factor)) residual = float64(shrink) / factor } // Zoom image, if necessary image, err = zoomImage(image, o.Zoom) if err != nil { return nil, err } // Rotate / flip image, if necessary image, err = rotateAndFlipImage(image, o) if err != nil { return nil, err } // Transform image, if necessary if shouldTransformImage(o, inWidth, inHeight) { image, err = transformImage(image, o, shrink, residual) if err != nil { return nil, err } } // Add watermark, if necessary image, err = watermakImage(image, o.Watermark) if err != nil { return nil, err } saveOptions := vipsSaveOptions{ Quality: o.Quality, Type: o.Type, Compression: o.Compression, Interlace: o.Interlace, NoProfile: o.NoProfile, Interpretation: o.Interpretation, } // Finally get the resultant buffer buf, err = vipsSave(image, saveOptions) if err != nil { return nil, err } return buf, nil }
// ThreadShutdown frees any thread-private data and flushes any profiling // information. This function needs to be called when a thread that has // been using vips exits or there will be memory leaks. It may be called // many times, and you can continue using vips after calling it. Calling it // too often will reduce performance. func ThreadShutdown() { C.vips_thread_shutdown() }
func AutoRotate(file string, o Options) ([]byte, error) { debug("%#+v", o) // detect (if possible) the file type /*typ := UNKNOWN switch { case bytes.Equal(buf[:2], MARKER_JPEG): typ = JPEG case bytes.Equal(buf[:2], MARKER_PNG): typ = PNG default: return nil, errors.New("unknown image format") } // create an image instance var image, tmpImage *C.struct__VipsImage // feed it switch typ { case JPEG: C.vips_jpegload_buffer_seq(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image) case PNG: C.vips_pngload_buffer_seq(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image) }*/ // create an image instance var image, tmpImage *C.struct__VipsImage image = C.vips_load_from_file(C.CString(file)) // cleanup defer func() { C.vips_thread_shutdown() C.vips_error_clear() }() if image == nil { return nil, catchVipsError() } rotate, flip := calculateRotationAndFlip(image, 0) if rotate == 0 && !flip { return nil, nil //Remain unchanged } ret := C.vips_autorotate(image, &tmpImage) if ret != 0 { return nil, catchVipsError() } if o.Quality == 0 { o.Quality = 100 } length := C.size_t(0) var ptr unsafe.Pointer switch o.Savetype { case WEBP: C.vips_webpsave_custom(tmpImage, &ptr, &length, C.int(o.Quality)) case PNG: C.vips_pngsave_custom(tmpImage, &ptr, &length, 1, C.int(o.Quality), 0) default: C.vips_jpegsave_custom(tmpImage, &ptr, &length, 1, C.int(o.Quality), 0) } C.g_object_unref(C.gpointer(tmpImage)) // get back the buffer var buf []byte buf = C.GoBytes(ptr, C.int(length)) C.g_free(C.gpointer(ptr)) return buf, nil }