Skip to content

feat: Preserve Aspect Ratio Option #71

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
14 changes: 10 additions & 4 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { createSkiaImageFromData } from './SkiaUtils';

type PixelFormat = Options<'uint8'>['pixelFormat'];

const WIDTH = 480;
const HEIGHT = 640;
const WIDTH = 300;
const HEIGHT = 300;
const TARGET_TYPE = 'uint8' as const;
const TARGET_FORMAT: PixelFormat = 'rgba';

Expand All @@ -39,8 +39,13 @@ export default function App() {
const updatePreviewImageFromData = useRunOnJS(
(data: SkData, pixelFormat: PixelFormat) => {
const image = createSkiaImageFromData(data, WIDTH, HEIGHT, pixelFormat);
if (image == null) {
data.dispose();
return;
}
previewImage.value?.dispose();
previewImage.value = image;
data.dispose();
},
[]
);
Expand All @@ -64,11 +69,10 @@ export default function App() {

const data = Skia.Data.fromBytes(result);
updatePreviewImageFromData(data, TARGET_FORMAT);
data.dispose();
const end = performance.now();

console.log(
`Resized ${frame.width}x${frame.height} into 100x100 frame (${
`Resized ${frame.width}x${frame.height} into ${WIDTH}x${HEIGHT} frame (${
result.length
}) in ${(end - start).toFixed(2)}ms`
);
Expand Down Expand Up @@ -119,5 +123,7 @@ const styles = StyleSheet.create({
position: 'absolute',
bottom: 80,
left: 20,
borderColor: '#F00',
borderWidth: 2,
},
});
63 changes: 47 additions & 16 deletions ios/ResizePlugin.mm
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ - (FrameBuffer*)convertFrameToARGB:(Frame*)frame {
return _argbBuffer;
}

- (FrameBuffer*)resizeARGB:(FrameBuffer*)buffer crop:(CGRect)crop scale:(CGSize)scale {
- (FrameBuffer*)resizeARGB:(FrameBuffer*)buffer crop:(CGRect)crop scale:(CGSize)scale preserveAspectRatio:(BOOL)preserveAspectRatio {
CGFloat cropWidth = crop.size.width;
CGFloat cropHeight = crop.size.height;
CGFloat cropX = crop.origin.x;
Expand Down Expand Up @@ -327,20 +327,44 @@ - (FrameBuffer*)resizeARGB:(FrameBuffer*)buffer crop:(CGRect)crop scale:(CGSize)
}
}

// Crop
vImage_Buffer cropped = (vImage_Buffer){.data = AdvancePtr(source->data, cropY * source->rowBytes + cropX * buffer.bytesPerPixel),
.height = (unsigned long)cropHeight,
.width = (unsigned long)cropWidth,
.rowBytes = source->rowBytes};
source = &cropped;

// Resize
vImage_Error error = vImageScale_ARGB8888(source, destination, _tempResizeBuffer, kvImageNoFlags);
if (error != kvImageNoError) {
[[unlikely]];
@throw [NSException exceptionWithName:@"Resize Error"
reason:[NSString stringWithFormat:@"Failed to resize ARGB buffer! Error: %zu", error]
userInfo:nil];

if (!preserveAspectRatio) {
// Crop
vImage_Buffer cropped = (vImage_Buffer){.data = AdvancePtr(source->data, cropY * source->rowBytes + cropX * buffer.bytesPerPixel),
.height = (unsigned long)cropHeight,
.width = (unsigned long)cropWidth,
.rowBytes = source->rowBytes};
source = &cropped;

// Resize
vImage_Error error = vImageScale_ARGB8888(source, destination, _tempResizeBuffer, kvImageNoFlags);
if (error != kvImageNoError) {
[[unlikely]];
@throw [NSException exceptionWithName:@"Resize Error"
reason:[NSString stringWithFormat:@"Failed to resize ARGB buffer! Error: %zu", error]
userInfo:nil];
}
}
else {
// Transform and pad
CGFloat scaleFactor = MIN(scale.width / buffer.width, scale.height / buffer.height);
CGAffineTransform cgTransform = CGAffineTransformIdentity;
cgTransform = CGAffineTransformScale(cgTransform, scaleFactor, scaleFactor);
vImage_AffineTransform vTransform = vImage_AffineTransform();
vTransform.a = cgTransform.a;
vTransform.b = cgTransform.b;
vTransform.c = cgTransform.c;
vTransform.d = cgTransform.d;
vTransform.tx = cgTransform.tx;
vTransform.ty = cgTransform.ty;
const Pixel_8888 backgroundColor = {255, 0, 0, 0};
vImage_Error error = vImageAffineWarp_ARGB8888(source, destination, nil, &vTransform, backgroundColor, kvImageBackgroundColorFill);
if (error != kvImageNoError) {
[[unlikely]];
@throw [NSException exceptionWithName:@"Resize Error"
reason:[NSString stringWithFormat:@"Failed to resize ARGB buffer! Error: %zu", error]
userInfo:nil];
}
}

return _resizeBuffer;
Expand Down Expand Up @@ -520,6 +544,13 @@ - (id)callback:(Frame*)frame withArguments:(NSDictionary*)arguments {
mirror = [mirrorParam boolValue];
}
NSLog(@"ResizePlugin: Mirror: %@", mirror ? @"YES" : @"NO");

NSNumber* preserveAspectRatioParam = arguments[@"preserveAspectRatio"];
BOOL preserveAspectRatio = NO;
if (preserveAspectRatioParam != nil) {
preserveAspectRatio = [preserveAspectRatioParam boolValue];
}
NSLog(@"ResizePlugin: Preserve Aspect Ratio: %@", preserveAspectRatio ? @"YES" : @"NO");

double cropWidth = (double)frame.width;
double cropHeight = (double)frame.height;
Expand Down Expand Up @@ -592,7 +623,7 @@ - (id)callback:(Frame*)frame withArguments:(NSDictionary*)arguments {
// 3. Resize
CGRect cropRect = CGRectMake(cropX, cropY, cropWidth, cropHeight);
CGSize scaleSize = CGSizeMake(scaleWidth, scaleHeight);
result = [self resizeARGB:result crop:cropRect scale:scaleSize];
result = [self resizeARGB:result crop:cropRect scale:scaleSize preserveAspectRatio:preserveAspectRatio];

// 4. Rotate
result = [self rotateARGBBuffer:result rotation:rotation];
Expand Down
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export interface Options<T extends DataType> {
* Scale the image to the given target size. This is applied after cropping.
*/
scale?: Size;
/**
* If set to `true`, the image will be resized to to fit within the dimensions specified by
* scale and padded, maintaining the aspect ratio of the original image.
*/
preserveAspectRatio?: boolean;
/**
* Rotate the image by a given amount of degrees, clockwise.
* @default '0deg'
Expand Down