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

feat: add format and quality parameters toDataURL function #2431

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9cc26da
feat: add type for toDataUrl options
bohdanprog Aug 28, 2024
b3c411a
feat: export ToDataUrlOptions type
bohdanprog Aug 28, 2024
20b35ce
feat: update Test2233, add data for test two options image format
bohdanprog Aug 28, 2024
b5c6cd7
feat: add implementation for iOS platform, now it's available to pass…
bohdanprog Aug 28, 2024
57be53a
feat: update iOS implementation, check quality value, and update func…
bohdanprog Aug 28, 2024
f93ea7a
feat: add android platform implementation for format and quality para…
bohdanprog Aug 28, 2024
b8aa9cd
feat: update test2233 example
bohdanprog Aug 28, 2024
f731f28
feat: extract out of class the function checkOptions, and add the fun…
bohdanprog Aug 28, 2024
3b4f12b
feat: add support for Macos platform format and quality parameters to…
bohdanprog Aug 28, 2024
78e31ad
feat: update test2233 example
bohdanprog Aug 28, 2024
6e762b9
feat: add to validateJpegQualityParameter check for macos
bohdanprog Aug 28, 2024
f525454
feat: update checkOptions and validateJpegQualityParameter functions
bohdanprog Aug 28, 2024
471c2db
feat: update web implementation add new parameters
bohdanprog Aug 28, 2024
ead5351
feat: update ToDataUrlOptions types, made validate function more unde…
bohdanprog Aug 29, 2024
5d07869
fix: upate web implementation and removed unnecessary comments
bohdanprog Sep 3, 2024
624f9f1
refactor: rename type from ToDataUrlOptions to DataUrlOptions
bohdanprog Sep 3, 2024
6720611
refactor: rename variable
bohdanprog Sep 3, 2024
683dcce
refactor: restore Test2233 example
bohdanprog Sep 3, 2024
88d412f
feat: update interface for JpegOptions and PngOptions
bohdanprog Sep 3, 2024
5fb2f9f
feat: update Svg example
bohdanprog Sep 3, 2024
5421aaf
feat: removed unimplemented method
bohdanprog Sep 3, 2024
26b15e2
feat: simplified getDataURLWithBounds function
bohdanprog Sep 3, 2024
85ce71c
feat: update Svg example
bohdanprog Sep 3, 2024
2b268ad
fix: fixed implementation without options, pass quality case it requi…
bohdanprog Sep 3, 2024
632d8d3
feat: modify the check for the lowest quality in validateJpegQualityP…
bohdanprog Sep 3, 2024
b6ea5b2
feat: removed extra check for the Android platform in the validateJpe…
bohdanprog Sep 4, 2024
1b722b9
feat: update Android implementation to make width and height paramete…
bohdanprog Sep 4, 2024
cf32be3
feat: update iOS implementation to make width and height parameters n…
bohdanprog Sep 4, 2024
f58e649
feat: update DataUrlOptions type,make width and height parameters not…
bohdanprog Sep 4, 2024
33c70db
feat: leave out quality value on web platform, don't pass default value
bohdanprog Sep 9, 2024
3edce70
Merge branch 'main' of github.com:software-mansion/react-native-svg i…
bohdanprog Sep 20, 2024
992d003
feat: update type safety, check if format value doesn't equal null, r…
bohdanprog Sep 20, 2024
ea3f8ac
feat: update Svg example
bohdanprog Sep 20, 2024
a91999c
feat: update type check on web version, also update types for native …
bohdanprog Sep 20, 2024
89963ce
feat: update svg example
bohdanprog Sep 20, 2024
9d7fef7
Merge branch 'main' of github.com:software-mansion/react-native-svg i…
bohdanprog Sep 24, 2024
d0f44b8
feat: create toDataUrlUtils for toDataUrl function
bohdanprog Sep 24, 2024
a7a77d1
chore: update DataUrlOptions imports
bohdanprog Sep 24, 2024
d84b048
chore: update Svg example
bohdanprog Sep 24, 2024
1ae4bdf
feat: add check if field size exist in options object
bohdanprog Sep 24, 2024
887eb51
fix: ios add new variable size, and extract width and height from siz…
bohdanprog Sep 24, 2024
13d38ec
chore: change JSdoc for toDataURL function
bohdanprog Sep 24, 2024
c2e2389
chore: changed quality check on web platform
bohdanprog Sep 24, 2024
f5c6464
Merge branch 'main' into feat/toDataURL-add-format-and-quality-parame…
bohdanprog Oct 2, 2024
ea9564f
Update JSDoc part of deprecated parameters
bohdanprog Oct 9, 2024
517a18e
Update JSDoc
bohdanprog Oct 9, 2024
21c5315
update description about function toDataUrl
bohdanprog Oct 9, 2024
211f7e9
chore: update properties description
bohdanprog Oct 9, 2024
3cdc9a0
Merge branch 'main' of github.com:software-mansion/react-native-svg i…
bohdanprog Oct 9, 2024
595d5d5
chore: update Podfile.lock
bohdanprog Oct 9, 2024
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
9 changes: 7 additions & 2 deletions android/src/main/java/com/horcrux/svg/SvgView.java
Original file line number Diff line number Diff line change
Expand Up @@ -356,15 +356,20 @@ String toDataURL() {
return Base64.encodeToString(bitmapBytes, Base64.NO_WRAP);
}

String toDataURL(int width, int height) {
String toDataURL(int width, int height, String format, Integer quality) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

clearChildCache();
drawChildren(new Canvas(bitmap));
clearChildCache();
this.invalidate();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
if (format.equals("jpeg")) {
int checkQualityValue = (quality != null) ? quality : 100;
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
bitmap.compress(Bitmap.CompressFormat.JPEG, checkQualityValue, stream);
} else {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
}
bitmap.recycle();
byte[] bitmapBytes = stream.toByteArray();
return Base64.encodeToString(bitmapBytes, Base64.NO_WRAP);
Expand Down
4 changes: 3 additions & 1 deletion android/src/main/java/com/horcrux/svg/SvgViewModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ public void run() {
});
} else {
if (options != null) {
Integer quality = options.hasKey("quality") ? options.getInt("quality") : null;
String format = options.hasKey("format") ? options.getString("format") : null;
successCallback.invoke(
svg.toDataURL(options.getInt("width"), options.getInt("height")));
svg.toDataURL(options.getInt("width"), options.getInt("height"), format, quality));
} else {
successCallback.invoke(svg.toDataURL());
}
Expand Down
3 changes: 2 additions & 1 deletion apple/Elements/RNSVGSvgView.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@

- (RNSVGFilter *)getDefinedFilter:(NSString *)filterName;

- (NSString *)getDataURLWithBounds:(CGRect)bounds;
- (NSString *)getDataURLWithBounds:(CGRect)bounds format:(NSString *)format;
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
- (NSString *)getDataURLWithBounds:(CGRect)bounds format:(NSString *)format quality:(CGFloat)quality;

- (CGRect)getContextBounds;

Expand Down
17 changes: 14 additions & 3 deletions apple/Elements/RNSVGSvgView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ - (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
return isPointInside ? self : nil;
}

- (NSString *)getDataURLWithBounds:(CGRect)bounds
- (NSString *)getDataURLWithBounds:(CGRect)bounds format:(NSString *)format quality:(CGFloat)quality
{
#if !TARGET_OS_OSX // [macOS]
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:bounds.size];
Expand All @@ -374,10 +374,21 @@ - (NSString *)getDataURLWithBounds:(CGRect)bounds
}];
#endif
#if !TARGET_OS_OSX // [macOS]
NSData *imageData = UIImagePNGRepresentation(image);
NSData *imageData;
if ([format isEqual:@"jpeg"]) {
imageData = UIImageJPEGRepresentation(image, quality);
} else {
imageData = UIImagePNGRepresentation(image);
}
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
#else // [macOS
NSData *imageData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext());
NSData *imageData;
NSImage *image = UIGraphicsGetImageFromCurrentImageContext();
if ([format isEqual:@"jpeg"]) {
imageData = UIImageJPEGRepresentation(image, quality);
} else {
imageData = UIImagePNGRepresentation(image);
}
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
UIGraphicsEndImageContext();
#endif // macOS]
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
7 changes: 5 additions & 2 deletions apple/RNSVGSvgViewModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ - (void)toDataURL:(nonnull NSNumber *)reactTag
if ([view isKindOfClass:[RNSVGSvgView class]]) {
RNSVGSvgView *svg = view;
if (options == nil) {
b64 = [svg getDataURLWithBounds:svg.boundingBox];
b64 = [svg getDataURLWithBounds:svg.boundingBox format:@"png"];
} else {
id width = [options objectForKey:@"width"];
id height = [options objectForKey:@"height"];
Expand All @@ -52,7 +52,10 @@ - (void)toDataURL:(nonnull NSNumber *)reactTag
NSInteger hi = (NSInteger)[h intValue];

CGRect bounds = CGRectMake(0, 0, wi, hi);
b64 = [svg getDataURLWithBounds:bounds];
NSString *format = [options objectForKey:@"format"];
NSNumber *qualityNumber = [options objectForKey:@"quality"];
CGFloat quality = qualityNumber ? [qualityNumber doubleValue] : 1.0;
b64 = [svg getDataURLWithBounds:bounds format:format quality:quality];
}
} else {
RCTLogError(@"Invalid svg returned from registry, expecting RNSVGSvgView, got: %@", view);
Expand Down
31 changes: 26 additions & 5 deletions apps/test-examples/src/Test2233.tsx
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,20 +1,41 @@
import * as React from 'react';
import {Button, SafeAreaView, View} from 'react-native';
import Svg, {Path} from 'react-native-svg';
import {Button, Dimensions, SafeAreaView, View} from 'react-native';
import Svg, {Path, type ToDataUrlOptions} from 'react-native-svg';

const SvgLogoWelcome = () => {
const ref = React.useRef<Svg | null>(null);

const {width, height} = Dimensions.get('screen');
const optionsWithJPEGFormat: ToDataUrlOptions = {
format: 'jpeg',
width,
height,
quality: 100,
};

const optionsWithPNGFormat: ToDataUrlOptions = {
format: 'png',
width,
height,
};

return (
<View>
<View
style={{
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
}}>
<Button
onPress={() => {
ref.current?.toDataURL(base64Image => {
console.log(base64Image, 'data');
});
}, optionsWithJPEGFormat);
}}
title="log"
/>
<Svg ref={ref} viewBox="0 0 24 24" fill="black">
<Svg ref={ref} viewBox="0 0 24 24" fill="black" width={250} height={250}>
<Path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10S2 17.514 2 12 6.486 2 12 2zm0-2C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm5.507 13.941c-1.512 1.195-3.174 1.931-5.506 1.931-2.334 0-3.996-.736-5.508-1.931L6 14.434C7.127 16.154 9.2 18 12.001 18c2.8 0 4.872-1.846 5.999-3.566l-.493-.493zM8.5 8a1.5 1.5 0 100 3 1.5 1.5 0 000-3zm7 0a1.5 1.5 0 100 3 1.5 1.5 0 000-3z" />
</Svg>
</View>
Expand Down
2 changes: 1 addition & 1 deletion src/ReactNativeSVG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export type { PolylineProps } from './elements/Polyline';
export type { RadialGradientProps } from './elements/RadialGradient';
export type { RectProps } from './elements/Rect';
export type { StopProps } from './elements/Stop';
export type { SvgProps } from './elements/Svg';
export type { SvgProps, ToDataUrlOptions } from './elements/Svg';
export type { SymbolProps } from './elements/Symbol';
export type { TextProps } from './elements/Text';
export type { TextPathProps } from './elements/TextPath';
Expand Down
24 changes: 18 additions & 6 deletions src/elements.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import type { PolylineProps } from './elements/Polyline';
import type { RadialGradientProps } from './elements/RadialGradient';
import type { RectProps } from './elements/Rect';
import type { StopProps } from './elements/Stop';
import type { SvgProps } from './elements/Svg';
import type { SvgProps, ToDataUrlOptions } from './elements/Svg';
import type { SymbolProps } from './elements/Symbol';
import type { TextProps } from './elements/Text';
import type { TextPathProps } from './elements/TextPath';
Expand Down Expand Up @@ -250,10 +250,7 @@ export class Stop extends WebShape<BaseProps & StopProps> {

export class Svg extends WebShape<BaseProps & SvgProps> {
tag = 'svg' as const;
toDataURL(
callback: (data: string) => void,
options: { width?: number; height?: number } = {}
) {
toDataURL(callback: (data: string) => void, options: ToDataUrlOptions) {
const ref = this.elementRef.current;

if (ref === null) {
Expand All @@ -264,6 +261,14 @@ export class Svg extends WebShape<BaseProps & SvgProps> {

const width = Number(options.width) || rect.width;
const height = Number(options.height) || rect.height;
// Set format, defaulting to 'png'
const format = options.format === 'jpeg' ? 'image/jpeg' : 'image/png';
// Get quality, only applicable for JPEG

let quality = 0.92; // Default quality is 0.92 for JPEG
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
if (options && options.format === 'jpeg' && options.quality) {
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
quality = options.quality;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this check and asimply assign options.quality anyway, since it's ignored when using lossless format like PNG

Suggested change
let quality = 0.92; // Default quality is 0.92 for JPEG
if (options && options.format === 'jpeg' && options.quality) {
quality = options.quality;
}
let quality = options.quality || 0.92; // Default quality is 0.92 for JPEG

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's better to leave it as it is because we also need to verify the format type.

Property 'quality' does not exist on type 'ToDataUrlOptions'.
Property 'quality' does not exist on type 'PngOptions'.


const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', `0 0 ${rect.width} ${rect.height}`);
Expand All @@ -278,7 +283,14 @@ export class Svg extends WebShape<BaseProps & SvgProps> {
canvas.height = height;
const context = canvas.getContext('2d');
context?.drawImage(img, 0, 0);
callback(canvas.toDataURL().replace('data:image/png;base64,', ''));
// Get the data URL with the specified format and quality
const dataURL =
format === 'image/jpeg'
? canvas.toDataURL(format, quality)
: canvas.toDataURL(format);
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
// Return the data URL, removing the prefix if it's PNG to match your original implementation
const base64Data = dataURL.replace(`data:${format};base64,`, '');
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
callback(base64Data);
};

img.src = `data:image/svg+xml;utf8,${encodeSvg(
Expand Down
40 changes: 39 additions & 1 deletion src/elements/Svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,40 @@
title?: string;
}

export type ToDataUrlOptions = JpegOptions | PngOptions;
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved

interface JpegOptions {
format: 'jpeg';
quality?: number; // Quality is optional but only available when format is 'jpeg'
width: number;
height: number;
}

interface PngOptions {
format: 'png';
width: number;
height: number;
}

function checkOptions(options?: ToDataUrlOptions) {
if (options && options?.format === 'jpeg') {
if (!validateJpegQualityParameter(options)) {
throw new Error('toDataURL: Invalid quality parameter for JPEG format.');
}
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
}
}

function validateJpegQualityParameter(options: JpegOptions): boolean {
if (options.quality && (options.quality < 0.1 || options.quality > 1)) {
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
// Android requires quality to be a number between 0 and 100
if (Platform.OS === 'android' && options.quality) {
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
options.quality = Math.round(options.quality * 100);
}
bohdanprog marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

export default class Svg extends Shape<SvgProps> {
static displayName = 'Svg';

Expand Down Expand Up @@ -81,10 +115,14 @@
root && root.setNativeProps(props);
};

toDataURL = (callback: (base64: string) => void, options?: object) => {
toDataURL = (
callback: (base64: string) => void,
options?: ToDataUrlOptions
) => {
if (!callback) {
return;
}
checkOptions(options);
const handle = findNodeHandle(this.root as Component);
const RNSVGSvgViewModule: Spec =
// eslint-disable-next-line @typescript-eslint/no-var-requires
Expand Down Expand Up @@ -185,7 +223,7 @@
props.transform = gStyle.transform;
gStyle.transform = undefined;
}
props.transform = extractTransformSvgView(props as any);

Check warning on line 226 in src/elements/Svg.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
}

const RNSVGSvg = Platform.OS === 'android' ? RNSVGSvgAndroid : RNSVGSvgIOS;
Expand Down
Loading