Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for cropping the original image before rescaling #168

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions lilliput.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
)

var (
ErrInvalidImage = errors.New("unrecognized image format")
ErrDecodingFailed = errors.New("failed to decode image")
ErrBufTooSmall = errors.New("buffer too small to hold image")
ErrFrameBufNoPixels = errors.New("Framebuffer contains no pixels")
ErrSkipNotSupported = errors.New("skip operation not supported by this decoder")
ErrEncodeTimeout = errors.New("encode timed out")
ErrInvalidImage = errors.New("unrecognized image format")
ErrDecodingFailed = errors.New("failed to decode image")
ErrBufTooSmall = errors.New("buffer too small to hold image")
ErrFrameBufNoPixels = errors.New("Framebuffer contains no pixels")
ErrSkipNotSupported = errors.New("skip operation not supported by this decoder")
ErrEncodeTimeout = errors.New("encode timed out")
ErrInvalidCropCoordinates = errors.New("invalid crop coordinates")

gif87Magic = []byte("GIF87a")
gif89Magic = []byte("GIF89a")
Expand Down
6 changes: 6 additions & 0 deletions opencv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,10 @@ int opencv_decoder_get_png_icc(void* src, size_t src_len, void* dest, size_t des

png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
return 0;
}

void opencv_mat_copy_to(const opencv_mat src, opencv_mat dst) {
auto srcMat = static_cast<const cv::Mat*>(src);
auto dstMat = static_cast<cv::Mat*>(dst);
srcMat->copyTo(*dstMat);
}
20 changes: 20 additions & 0 deletions opencv.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,26 @@ func (f *Framebuffer) Fit(width, height int, dst *Framebuffer) error {
return nil
}

// CropTo performs a cropping transform on the Framebuffer
func (f *Framebuffer) CropTo(x, y, width, height int, dst *Framebuffer) error {
if f.mat == nil {
return ErrFrameBufNoPixels
}

croppedMat := C.opencv_mat_crop(f.mat, C.int(x), C.int(y), C.int(width), C.int(height))
if croppedMat == nil {
return ErrInvalidCropCoordinates
}
defer C.opencv_mat_release(croppedMat)

err := dst.resizeMat(width, height, f.pixelType)
if err != nil {
return err
}
C.opencv_mat_copy_to(croppedMat, dst.mat)
return nil
}

// Width returns the width of the contained pixel data in number of pixels. This may
// differ from the capacity of the framebuffer.
func (f *Framebuffer) Width() int {
Expand Down
1 change: 1 addition & 0 deletions opencv.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ void opencv_encoder_release(opencv_encoder e);
bool opencv_encoder_write(opencv_encoder e, const opencv_mat src, const int* opt, size_t opt_len);
int opencv_decoder_get_jpeg_icc(void* src, size_t src_len, void* dest, size_t dest_len);
int opencv_decoder_get_png_icc(void* src, size_t src_len, void* dest, size_t dest_len);
void opencv_mat_copy_to(const opencv_mat src, opencv_mat dst);

#ifdef __cplusplus
}
Expand Down
58 changes: 58 additions & 0 deletions opencv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,61 @@ func TestICC(t *testing.T) {
})
}
}

func TestCropTo(t *testing.T) {
// Read a test image
imgData, err := ioutil.ReadFile("testdata/ferry_sunset.jpg")
if err != nil {
t.Fatalf("Failed to read image file: %v", err)
}

// Create a new decoder for the image
decoder, err := newOpenCVDecoder(imgData)
if err != nil {
t.Fatalf("Failed to create decoder: %v", err)
}
defer decoder.Close()

// Get the image header
header, err := decoder.Header()
if err != nil {
t.Fatalf("Failed to get the header: %v", err)
}

// Create a new framebuffer and decode the image into it
framebuffer := NewFramebuffer(header.width, header.height)
if err = decoder.DecodeTo(framebuffer); err != nil {
t.Fatalf("DecodeTo failed unexpectedly: %v", err)
}

// Define the crop rectangle
cropX, cropY, cropWidth, cropHeight := 10, 10, 100, 100

// Perform the crop operation
err = framebuffer.CropTo(cropX, cropY, cropWidth, cropHeight)
if err != nil {
t.Fatalf("CropTo failed unexpectedly: %v", err)
}

// Check the dimensions of the cropped image
if framebuffer.Width() != cropWidth || framebuffer.Height() != cropHeight {
t.Fatalf("Cropped dimensions are incorrect, got width = %d, height = %d, expected width = %d, height = %d",
framebuffer.Width(), framebuffer.Height(), cropWidth, cropHeight)
}

// Optional: Encode the cropped image to verify the integrity
dstBuf := make([]byte, 1024*1024)
encoder, err := newOpenCVEncoder(".jpg", decoder, dstBuf)
if err != nil {
t.Fatalf("Failed to create encoder: %v", err)
}
defer encoder.Close()

encodedData, err := encoder.Encode(framebuffer, nil)
if err != nil {
t.Fatalf("Encode failed unexpectedly: %v", err)
}
if len(encodedData) == 0 {
t.Fatalf("Encoded data is empty, but it should not be")
}
}
40 changes: 39 additions & 1 deletion ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ type ImageOptions struct {

// This is a best effort timeout when encoding multiple frames
EncodeTimeout time.Duration

// CropCoordinates specify the rectangle of the original image to be cropped before resizing
CropCoordinates *CropCoordinates
}

// CropCoordinates specifies a rectangle for cropping
type CropCoordinates struct {
X int
Y int
Width int
Height int
}

// ImageOps is a reusable object that can resize and encode images.
Expand Down Expand Up @@ -119,6 +130,17 @@ func (o *ImageOps) resize(d Decoder, width, height int) (bool, error) {
return true, nil
}

func (o *ImageOps) crop(d Decoder, x, y, width, height int) (bool, error) {
active := o.active()
secondary := o.secondary()
err := active.CropTo(x, y, width, height, secondary)
if err != nil {
return false, err
}
o.swap()
return true, nil
}

func (o *ImageOps) normalizeOrientation(orientation ImageOrientation) {
active := o.active()
active.OrientationTransform(orientation)
Expand Down Expand Up @@ -188,6 +210,18 @@ func (o *ImageOps) Transform(d Decoder, opt *ImageOptions, dst []byte) ([]byte,

o.normalizeOrientation(h.Orientation())

var swappedCrop bool
if opt.CropCoordinates != nil {
if opt.CropCoordinates.X+opt.CropCoordinates.Width > h.Width() || opt.CropCoordinates.Y+opt.CropCoordinates.Height > h.Height() {
return nil, ErrInvalidCropCoordinates
}

swappedCrop, err = o.crop(d, opt.CropCoordinates.X, opt.CropCoordinates.Y, opt.CropCoordinates.Width, opt.CropCoordinates.Height)
if err != nil {
return nil, err
}
}

var swapped bool
if opt.ResizeMethod == ImageOpsFit {
swapped, err = o.fit(d, opt.Width, opt.Height)
Expand All @@ -197,6 +231,10 @@ func (o *ImageOps) Transform(d Decoder, opt *ImageOptions, dst []byte) ([]byte,
swapped, err = false, nil
}

if swappedCrop {
swapped = !swapped
}

if err != nil {
return nil, err
}
Expand Down Expand Up @@ -232,7 +270,7 @@ func (o *ImageOps) Transform(d Decoder, opt *ImageOptions, dst []byte) ([]byte,

// content == nil and err == nil -- this is encoder telling us to do another frame

// for mulitple frames/gifs we need the decoded frame to be active again
// for multiple frames/gifs we need the decoded frame to be active again
if swapped {
o.swap()
}
Expand Down