Optimize compressImage method to also use binary search (#7490)
* use binary search for compressImage * use binary search for doResize (mobile version) * use binary search for doResize (web version) * use safeDeleteAsync in compressImage --------- Co-authored-by: nguyen <nguyensvo123@gmail.com>
This commit is contained in:
+20
-7
@@ -178,15 +178,20 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
|
|||||||
height: imageRes.height,
|
height: imageRes.height,
|
||||||
})
|
})
|
||||||
|
|
||||||
for (let i = 0; i < 9; i++) {
|
let minQualityPercentage = 0
|
||||||
// nearest 10th
|
let maxQualityPercentage = 101 // exclusive
|
||||||
const quality = Math.round((1 - 0.1 * i) * 10) / 10
|
let newDataUri
|
||||||
|
|
||||||
|
while (maxQualityPercentage - minQualityPercentage > 1) {
|
||||||
|
const qualityPercentage = Math.round(
|
||||||
|
(maxQualityPercentage + minQualityPercentage) / 2,
|
||||||
|
)
|
||||||
const resizeRes = await manipulateAsync(
|
const resizeRes = await manipulateAsync(
|
||||||
localUri,
|
localUri,
|
||||||
[{resize: newDimensions}],
|
[{resize: newDimensions}],
|
||||||
{
|
{
|
||||||
format: SaveFormat.JPEG,
|
format: SaveFormat.JPEG,
|
||||||
compress: quality,
|
compress: qualityPercentage / 100,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -198,8 +203,8 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fileInfo.size < opts.maxSize) {
|
if (fileInfo.size < opts.maxSize) {
|
||||||
safeDeleteAsync(imageRes.uri)
|
minQualityPercentage = qualityPercentage
|
||||||
return {
|
newDataUri = {
|
||||||
path: normalizePath(resizeRes.uri),
|
path: normalizePath(resizeRes.uri),
|
||||||
mime: 'image/jpeg',
|
mime: 'image/jpeg',
|
||||||
size: fileInfo.size,
|
size: fileInfo.size,
|
||||||
@@ -207,9 +212,17 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
|
|||||||
height: resizeRes.height,
|
height: resizeRes.height,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
safeDeleteAsync(resizeRes.uri)
|
maxQualityPercentage = qualityPercentage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
safeDeleteAsync(resizeRes.uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newDataUri) {
|
||||||
|
safeDeleteAsync(imageRes.uri)
|
||||||
|
return newDataUri
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`This image is too big! We couldn't compress it down to ${opts.maxSize} bytes`,
|
`This image is too big! We couldn't compress it down to ${opts.maxSize} bytes`,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -72,17 +72,28 @@ interface DoResizeOpts {
|
|||||||
async function doResize(dataUri: string, opts: DoResizeOpts): Promise<RNImage> {
|
async function doResize(dataUri: string, opts: DoResizeOpts): Promise<RNImage> {
|
||||||
let newDataUri
|
let newDataUri
|
||||||
|
|
||||||
for (let i = 0; i <= 10; i++) {
|
let minQualityPercentage = 0
|
||||||
newDataUri = await createResizedImage(dataUri, {
|
let maxQualityPercentage = 101 //exclusive
|
||||||
|
|
||||||
|
while (maxQualityPercentage - minQualityPercentage > 1) {
|
||||||
|
const qualityPercentage = Math.round(
|
||||||
|
(maxQualityPercentage + minQualityPercentage) / 2,
|
||||||
|
)
|
||||||
|
const tempDataUri = await createResizedImage(dataUri, {
|
||||||
width: opts.width,
|
width: opts.width,
|
||||||
height: opts.height,
|
height: opts.height,
|
||||||
quality: 1 - i * 0.1,
|
quality: qualityPercentage / 100,
|
||||||
mode: opts.mode,
|
mode: opts.mode,
|
||||||
})
|
})
|
||||||
if (getDataUriSize(newDataUri) < opts.maxSize) {
|
|
||||||
break
|
if (getDataUriSize(tempDataUri) < opts.maxSize) {
|
||||||
|
minQualityPercentage = qualityPercentage
|
||||||
|
newDataUri = tempDataUri
|
||||||
|
} else {
|
||||||
|
maxQualityPercentage = qualityPercentage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newDataUri) {
|
if (!newDataUri) {
|
||||||
throw new Error('Failed to compress image')
|
throw new Error('Failed to compress image')
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-9
@@ -13,7 +13,7 @@ import {
|
|||||||
import {nanoid} from 'nanoid/non-secure'
|
import {nanoid} from 'nanoid/non-secure'
|
||||||
|
|
||||||
import {POST_IMG_MAX} from '#/lib/constants'
|
import {POST_IMG_MAX} from '#/lib/constants'
|
||||||
import {getImageDim} from '#/lib/media/manip'
|
import {getImageDim, safeDeleteAsync} from '#/lib/media/manip'
|
||||||
import {openCropper} from '#/lib/media/picker'
|
import {openCropper} from '#/lib/media/picker'
|
||||||
import {getDataUriSize} from '#/lib/media/util'
|
import {getDataUriSize} from '#/lib/media/util'
|
||||||
import {isIOS, isNative} from '#/platform/detection'
|
import {isIOS, isNative} from '#/platform/detection'
|
||||||
@@ -212,15 +212,20 @@ export async function compressImage(img: ComposerImage): Promise<ImageMeta> {
|
|||||||
const [w, h] = containImageRes(source.width, source.height, POST_IMG_MAX)
|
const [w, h] = containImageRes(source.width, source.height, POST_IMG_MAX)
|
||||||
const cacheDir = isNative && getImageCacheDirectory()
|
const cacheDir = isNative && getImageCacheDirectory()
|
||||||
|
|
||||||
for (let i = 10; i > 0; i--) {
|
let minQualityPercentage = 0
|
||||||
// Float precision
|
let maxQualityPercentage = 101 // exclusive
|
||||||
const factor = i / 10
|
let newDataUri
|
||||||
|
|
||||||
|
while (maxQualityPercentage - minQualityPercentage > 1) {
|
||||||
|
const qualityPercentage = Math.round(
|
||||||
|
(maxQualityPercentage + minQualityPercentage) / 2,
|
||||||
|
)
|
||||||
|
|
||||||
const res = await manipulateAsync(
|
const res = await manipulateAsync(
|
||||||
source.path,
|
source.path,
|
||||||
[{resize: {width: w, height: h}}],
|
[{resize: {width: w, height: h}}],
|
||||||
{
|
{
|
||||||
compress: factor,
|
compress: qualityPercentage / 100,
|
||||||
format: SaveFormat.JPEG,
|
format: SaveFormat.JPEG,
|
||||||
base64: true,
|
base64: true,
|
||||||
},
|
},
|
||||||
@@ -229,17 +234,23 @@ export async function compressImage(img: ComposerImage): Promise<ImageMeta> {
|
|||||||
const base64 = res.base64
|
const base64 = res.base64
|
||||||
|
|
||||||
if (base64 !== undefined && getDataUriSize(base64) <= POST_IMG_MAX.size) {
|
if (base64 !== undefined && getDataUriSize(base64) <= POST_IMG_MAX.size) {
|
||||||
return {
|
minQualityPercentage = qualityPercentage
|
||||||
|
newDataUri = {
|
||||||
path: await moveIfNecessary(res.uri),
|
path: await moveIfNecessary(res.uri),
|
||||||
width: res.width,
|
width: res.width,
|
||||||
height: res.height,
|
height: res.height,
|
||||||
mime: 'image/jpeg',
|
mime: 'image/jpeg',
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
maxQualityPercentage = qualityPercentage
|
||||||
|
if (cacheDir) {
|
||||||
|
await safeDeleteAsync(res.uri)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cacheDir) {
|
if (newDataUri) {
|
||||||
await deleteAsync(res.uri)
|
return newDataUri
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unable to compress image`)
|
throw new Error(`Unable to compress image`)
|
||||||
|
|||||||
Reference in New Issue
Block a user