В настоящее время API ArcFace 2.0 поддерживает несколько форматов изображений:BGR24
,NV21
,NV12
,I420
,YUYV
(Android и IOS поддерживают только некоторые из них). Далее мы представим эти форматы изображений и некоторые методы преобразования.
1. Введение в соответствующее цветовое пространство изображения
1. Цветовое пространство RGB
RGB颜色空间
Основываясь на трех основных цветах: красном, зеленом и синем, выполняются различные степени наложения для получения насыщенных и обширных цветов, поэтому этот режим широко известен как режим трех основных цветов.
Общие форматы RGB:RGB_565
,RGB_888
,ARGB_8888
,ARGB_4444
Ждать.
2. Цветовое пространство YUV
существуетYUV颜色空间
Среди них Y используется для представления яркости, а U и V используются для представления цветности.
Общие форматы YUV имеют следующие категории:planar:Y, U, V сохраняются постоянно, напримерI420
,YV12
packed:Y, U, V перекрестное хранение, напримерYUYV
semi-planar:Y хранится непрерывно, а U и V хранятся перекрестно, напримерNV21
,NV12
2. Введение в связанные форматы изображений
1. Формат изображения BGR24
BGR24
Формат изображения — это формат, использующий 24 бита на пиксель (бит на пиксель). Каждый цветовой канал B, G, R занимает по 8 бит на пикс.
Аранжировано как:
B G R B G R B G R B G R B G R B G R B G R B G R
B G R B G R B G R B G R B G R B G R B G R B G R
B G R B G R B G R B G R B G R B G R B G R B G R
B G R B G R B G R B G R B G R B G R B G R B G R
2. Формат изображения NV21
NV21
Формат изображения принадлежитYUVв цветовом пространствеYUV420SP
Формат, каждые четыре компонента Y имеют общий набор компонентов U и компонентов V, Y сортируется последовательно, а U и V перекрестно сортируются.
Аранжировано как:
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
V U V U V U V U
V U V U V U V U
3. Формат изображения NV12
NV12
Формат изображения принадлежитYUVв цветовом пространствеYUV420SP
Формат, каждые четыре компонента Y совместно используют набор компонентов U и компонентов V, Y сортируется последовательно, а U и V перекрестно сортируются (NV12
иNV21
Просто позиции U и V поменялись местами).
Аранжировано как:
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
U V U V U V U V
U V U V U V U V
4. Формат изображения I420
I420
Формат изображения принадлежитYUVв цветовом пространствеYUV420P
Формат, каждые четыре компонента Y имеют общий набор компонентов U и компонентов V, а Y, U и V расположены последовательно. (Чтобы объяснить общую связь между Y, U и V, ни U, ни V не переносят строку)
Аранжировано как:
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
U U U U U U U U
V V V V V V V V
5. Формат изображения YV12
YV12
Формат изображения принадлежитYUVв цветовом пространствеYUV420P
Формат: каждые четыре компонента Y совместно используют набор компонентов U и компонентов V, а Y, U и V упорядочены последовательно (чтобы объяснить общую связь между Y, U и V, U и V не переносятся строками). ) (YV12
иI420
Просто позиции U и V поменялись местами).
Аранжировано как:
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
V V V V V V V V
U U U U U U U U
6. Формат изображения YUYV
YUYV
Формат изображения принадлежитYUVв цветовом пространствеYUV422
Format каждые два компонента Y совместно используют набор компонентов U и компонентов V, а Y, U и V перекрестно отсортированы.
Аранжировано как:
Y U Y V Y U Y V Y U Y V Y U Y V
Y U Y V Y U Y V Y U Y V Y U Y V
Y U Y V Y U Y V Y U Y V Y U Y V
Y U Y V Y U Y V Y U Y V Y U Y V
3. Преобразование формата изображения
Из-за разнообразия форматов изображений существует бесчисленное множество методов преобразования, если вы понимаетеYUV
иRGB
Благодаря тому, как организованы данные, самостоятельное написание кода преобразования изображений не занимает много времени. Код Java части преобразования изображения, указанный ниже, приведен для справки.
1. ИзBitmap
получено вARGB_8888
Данные формата изображения (платформа Android)
Bitmap
Поддерживается несколько форматов:ALPHA_8,RGB_565,ARGB_4444,ARGB_8888,RGBA_F16,HARDWARE
. Наш текущий основной выборARGB_8888
Преобразование форматов.
мы можем использоватьBitmap
в классеpublic void getPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height)
способ получитьint[]
argb данные типа илиpublic void copyPixelsToBuffer (Buffer dst)
способ получитьbyte[]
ТипARGB_8888
данные.
2. ARGB_8888
преобразовать вBGR_24
Например, для изображения 4x2,ARGB_8888
Содержание формата:
A1 R1 G1 B1 A2 R2 G2 B2 A3 R3 G3 B3 A4 R4 G4 B4
A5 R5 G5 B5 A6 R6 G6 B6 A7 R7 G7 B7 A8 R8 G8 B8
Затем, если вам нужно преобразовать вBGR_24
, содержимое станет:
B1 G1 R1 B2 G2 R2 B3 G3 R3 B4 G4 R4
B5 G5 R5 B6 G6 R6 B7 G7 R7 B8 G8 R8
BGR_24
Содержание 3byte
Одна группа,ARGB_8888
Содержание 4byte
Одна группа. Поэтому для первой группыARGB_8888(A1 R1 G1 B1)
и первая группаBGR_24(B1 G1 R1)
, и соответствующее отношение:
bgr24[0] = argb8888[3];
bgr24[1] = argb8888[2];
bgr24[2] = argb8888[1];
Соответствующий код преобразования:
public static byte[] argb8888ToBgr24(byte[] argb8888) {
if (argb8888 == null){
throw new IllegalArgumentException("invalid image params!");
}
int groupNum = argb8888.length / 4;
byte[] bgr24 = new byte[groupNum * 3];
int bgr24Index = 0;
int argb8888Index = 0;
for (int i = 0; i < groupNum; i++) {
bgr24[bgr24Index] = argb8888[argb8888Index + 2];
bgr24[bgr24Index + 1] = argb8888[argb8888Index + 1];
bgr24[bgr24Index + 2] = argb8888[argb8888Index];
bgr24Index += 3;
argb8888Index += 4;
}
return bgr24;
}
3. ARGB_8888
преобразовать вNV21
rgb
изменятьyuv
Алгоритм:
int y = (66 * r + 129 * g + 25 * b + 128 >> 8) + 16;
int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128;
int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128;
Метод преобразования:
-
int[]
ТипARGB_8888
данные преобразуются вNV21
:
private static byte[] argbToNv21(int[] argb, int width, int height) {
if (argb == null || argb.length == 0 || width * height != argb.length) {
throw new IllegalArgumentException("invalid image params!");
}
int yIndex = 0;
int uvIndex = width * height;
int argbIndex = 0;
byte[] nv21 = new byte[width * height * 3 / 2];
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
//对于int型color数据,格式为0xAARRGGBB,可进行与运算后移位取对应A R G B,
//但是该YUV转换公式中不需要ALPHA,因此我们只需要取 R G B 即可。
int r = (argb[argbIndex] & 0xFF0000) >> 16;
int g = (argb[argbIndex] & 0x00FF00) >> 8;
int b = argb[argbIndex] & 0x0000FF;
//获取该像素点的R G B,并转换为Y U V,但byte范围是0x00~0xFF,因此在赋值时还需进行判断
int y = (66 * r + 129 * g + 25 * b + 128 >> 8) + 16;
nv21[yIndex++] = (byte) (y < 0 ? 0 : (y > 0xFF ? 0xFF : y));
if ((j & 1) == 0 && (argbIndex & 1) == 0 && uvIndex < nv21.length - 2) {
int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128;
int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128;
nv21[uvIndex++] = (byte) (v < 0 ? 0 : (v > 0xFF ? 0xFF : v));
nv21[uvIndex++] = (byte) (u < 0 ? 0 : (u > 0xFF ? 0xFF : u));
}
++argbIndex;
}
}
return nv21;
}
-
byte[]
ТипARGB_8888
данные преобразуются вNV21
(Принцип тот же, что и в способе 1):
private static byte[] argbToNv21(byte[] argb, int width, int height) {
if (argb == null || argb.length == 0 || width * height * 4 != argb.length) {
throw new IllegalArgumentException("invalid image params!");
}
int yIndex = 0;
int uvIndex = width * height;
int argbIndex = 0;
byte[] nv21 = new byte[width * height * 3 / 2];
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
argbIndex++;
int r = argb[argbIndex++];
int g = argb[argbIndex++];
int b = argb[argbIndex++];
r &= 0x000000FF;
g &= 0x000000FF;
b &= 0x000000FF;
int y = ((66 * r + 129 * g + 25 * b + 128 >> 8) + 16);
nv21[yIndex++] = (byte) (y > 0xFF ? 0xFF : (y < 0 ? 0 : y));
if ((j & 1) == 0 && ((argbIndex >> 2) & 1) == 0 && uvIndex < nv21.length - 2) {
int u = ((-38 * r - 74 * g + 112 * b + 128 >> 8) + 128);
int v = ((112 * r - 94 * g - 18 * b + 128 >> 8) + 128);
nv21[uvIndex++] = (byte) (v > 0xFF ? 0xFF : (v < 0 ? 0 : v));
nv21[uvIndex++] = (byte) (u > 0xFF ? 0xFF : (u < 0 ? 0 : u));
}
}
}
return nv21;
}
4. NV21
преобразовать вBGR24
yuv
изменятьrgb
алгоритм:
int r = (int) ((y & 0xFF) + 1.4075 * ((v & 0xFF) - 128));
int g = (int) ((y & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128));
int b = (int) ((y & 0xFF) + 1.779 * ((u & 0xFF) - 128));
Метод преобразования:
private static byte[] nv21ToBgr24(byte[] nv21, int width, int height) {
if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
throw new IllegalArgumentException("invalid image params!");
}
byte[] bgr24 = new byte[width * height * 3];
int bgrLineSize = width * 3;
//偶数行的bgr数据下标
int evenLineBgrIndex = 0;
//奇数行的bgr数据下标
int oddLineBgrIndex = bgrLineSize;
//当前一行y数据最左边的下标
int yLineStart = 0;
//uv数据的下标
int uvIndex = width * height;
//由于NV21的共用关系,每2行做一次转换
for (int i = 0; i < height; i += 2) {
for (int widthOffset = 0; widthOffset < width; widthOffset++) {
byte v = nv21[uvIndex];
byte u = nv21[uvIndex + 1];
byte yEven = nv21[yLineStart + widthOffset];
byte yOdd = nv21[yLineStart + width + widthOffset];
//偶数行YUV转RGB
int r, g, b;
r = (int) ((yEven & 0xFF) + 1.4075 * ((v & 0xFF) - 128));
g = (int) ((yEven & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128));
b = (int) ((yEven & 0xFF) + 1.779 * ((u & 0xFF) - 128));
r = r < 0 ? 0 : r > 0xFF ? 0xFF : r;
g = g < 0 ? 0 : g > 0xFF ? 0xFF : g;
b = b < 0 ? 0 : b > 0xFF ? 0xFF : b;
bgr24[evenLineBgrIndex++] = (byte) b;
bgr24[evenLineBgrIndex++] = (byte) g;
bgr24[evenLineBgrIndex++] = (byte) r;
//奇数行YUV转RGB
r = (int) ((yOdd & 0xFF) + 1.4075 * ((v & 0xFF) - 128));
g = (int) ((yOdd & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128));
b = (int) ((yOdd & 0xFF) + 1.779 * ((u & 0xFF) - 128));
r = r < 0 ? 0 : r > 0xFF ? 0xFF : r;
g = g < 0 ? 0 : g > 0xFF ? 0xFF : g;
b = b < 0 ? 0 : b > 0xFF ? 0xFF : b;
bgr24[oddLineBgrIndex++] = (byte) b;
bgr24[oddLineBgrIndex++] = (byte) g;
bgr24[oddLineBgrIndex++] = (byte) r;
//每两个y将uv下标增1
if ((widthOffset & 1) == 1) {
uvIndex += 2;
}
}
//由于在内层循环中已经做过width * 3次自增,所以外层循环中只需要增加一行
evenLineBgrIndex += bgrLineSize;
oddLineBgrIndex += bgrLineSize;
//y增2行
yLineStart += width * 2;
}
return bgr24;
}
5. NV12
иNV21
менять
NV21
иNV12
Просто расположение данных U и V отличается, поэтомуNV21
преобразовать вNV12
Код также работает дляNV12
преобразовать вNV21
. Вы можете обратиться к следующему коду:
public static byte[] nv21ToNv12(byte[] nv21, int width, int height) {
if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
throw new IllegalArgumentException("invalid image params!");
}
final int ySize = width * height;
int totalSize = width * height * 3 / 2;
byte[] nv12 = new byte[nv21.length];
//复制Y
System.arraycopy(nv21, 0, nv12, 0, ySize);
//UV互换
for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
nv12[uvIndex] = nv21[uvIndex + 1];
nv12[uvIndex + 1] = nv21[uvIndex];
}
return nv12;
}
6. NV21
изменятьYV12
NV21
превратиться вYV12
Процесс в основном заключается в изменении перекрестной сортировки своих УФ-данных на непрерывную сортировку. Вы можете обратиться к следующему коду:
public static byte[] nv21ToYv12(byte[] nv21, int width, int height) {
if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
throw new IllegalArgumentException("invalid image params!");
}
final int ySize = width * height;
int totalSize = width * height * 3 / 2;
byte[] yv12 = new byte[nv21.length];
int yv12UIndex = ySize;
int yv12VIndex = ySize * 5 / 4;
//复制Y
System.arraycopy(nv21, 0, yv12, 0, ySize);
//复制UV
for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
yv12[yv12UIndex++] = nv21[uvIndex];
yv12[yv12VIndex++] = nv21[uvIndex + 1];
}
return yv12;
}
7. YUYV
изменятьNV12
существуетYUYV
формат, дваY
поделиться группойU
иV
,иNV12
четыреY
поделиться группойU
иV
, так что этоYUV422
изменятьYUV420
процесс, нужно отказаться от половиныU
иV
. Вы можете обратиться к следующему коду:
public static byte[] yuyvToNv12(byte[] yuyv, int width, int height) {
if (yuyv == null || yuyv.length == 0) {
throw new IllegalArgumentException("invalid image params!");
}
int ySize = yuyv.length / 2;
byte[] nv12 = new byte[yuyv.length * 3 / 4];
int nv12YIndex = 0;
int nv12UVIndex = ySize;
boolean copyUV = false;
int lineDataSize = width * 2;
for (int i = 0, yuyvIndex = 0; i < height; i++, yuyvIndex += lineDataSize) {
if (copyUV) {
for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
//复制Y
nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset];
nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2];
//复制UV
nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 1];
nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 3];
}
} else {
for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
//复制Y
nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset];
nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2];
}
}
copyUV = !copyUV;
}
return nv12;
}
8. I420
иYV12
менять
I420
иYV12
ТолькоU
иV
Расположение данных другое, поэтомуI420
преобразовать вYV12
Код также работает дляYV12
преобразовать вI420
. Вы можете обратиться к следующему коду:
public static byte[] i420ToYv12(byte[] i420) {
if (i420 == null || i420.length == 0 || i420.length % 6 != 0) {
throw new IllegalArgumentException("invalid image params!");
}
int ySize = i420.length * 2 / 3;
int uvSize = i420.length / 6;
byte[] yv12 = new byte[i420.length];
//复制Y
System.arraycopy(i420, 0, yv12, 0, ySize);
//UV互换
System.arraycopy(i420, ySize, yv12, ySize + uvSize, uvSize);
System.arraycopy(i420, ySize + uvSize, yv12, ySize, uvSize);
return yv12;
}
9. I420
преобразовать вYUYV
I420
иYUYV
по сравнению с,I420
изU
иV
ТолькоYUYV
половина, что являетсяYUV420
изменятьYUV422
процесс, дефектные данные могут быть повторно использованы толькоU
иV
макияж, мириться.
public static byte[] i420ToYuyv(byte[] i420, int width, int height) {
if (i420 == null || i420.length == 0 || i420.length != width * height * 3 / 2) {
throw new IllegalArgumentException("invalid image params!");
}
byte[] yuyv = new byte[width * height * 2];
int yuyvLineSize = width * 2;
int i420YIndex = 0;
int i420UIndex = width * height;
int i420VIndex = width * height * 5 / 4;
int yuyvLineStart = 0;
for (int i = 0; i < height; i += 2) {
for (int lineOffset = 0; lineOffset < yuyvLineSize; lineOffset += 4) {
byte u = i420[i420UIndex++];
byte v = i420[i420VIndex++];
//偶数行数据赋值
int yuyvOffset = yuyvLineStart + lineOffset;
yuyv[yuyvOffset] = i420[i420YIndex];
yuyv[yuyvOffset + 1] = u;
yuyv[yuyvOffset + 2] = i420[i420YIndex + 1];
yuyv[yuyvOffset + 3] = v;
//奇数行数据赋值
int yuyvNextLineOffset = yuyvLineStart + yuyvLineSize + lineOffset;
yuyv[yuyvNextLineOffset] = i420[i420YIndex + width];
yuyv[yuyvNextLineOffset + 1] = u;
yuyv[yuyvNextLineOffset + 2] = i420[i420YIndex + width + 1];
yuyv[yuyvNextLineOffset + 3] = v;
i420YIndex += 2;
}
i420YIndex += width;
yuyvLineStart += (width << 2);
}
return yuyv;
}
4. Обрезка изображения
Как и в случае преобразования формата, обрезка изображения не представляет сложности, если вы знаете, как расположены изображения. В этой статьеRGB
Обрезка изображения в цветовом пространстве иYUV
Обрезка изображения в цветовом пространстве.
1. Кроп НВ21
public static byte[] i420ToYv12(byte[] i420) {
if (i420 == null || i420.length == 0 || i420.length % 6 != 0) {
throw new IllegalArgumentException("invalid image params!");
}
int ySize = i420.length * 2 / 3;
int uvSize = i420.length / 6;
byte[] yv12 = new byte[i420.length];
//复制Y
System.arraycopy(i420, 0, yv12, 0, ySize);
//UV互换
System.arraycopy(i420, ySize, yv12, ySize + uvSize, uvSize);
System.arraycopy(i420, ySize + uvSize, yv12, ySize, uvSize);
return yv12;
}
9. I420
преобразовать вYUYV
I420
иYUYV
по сравнению с,I420
изU
иV
ТолькоYUYV
половина, что являетсяYUV420
изменятьYUV422
процесс, дефектные данные могут быть повторно использованы толькоU
иV
макияж, мириться.
public static byte[] i420ToYuyv(byte[] i420, int width, int height) {
if (i420 == null || i420.length == 0 || i420.length != width * height * 3 / 2) {
throw new IllegalArgumentException("invalid image params!");
}
byte[] yuyv = new byte[width * height * 2];
int yuyvLineSize = width * 2;
int i420YIndex = 0;
int i420UIndex = width * height;
int i420VIndex = width * height * 5 / 4;
int yuyvLineStart = 0;
for (int i = 0; i < height; i += 2) {
for (int lineOffset = 0; lineOffset < yuyvLineSize; lineOffset += 4) {
byte u = i420[i420UIndex++];
byte v = i420[i420VIndex++];
//偶数行数据赋值
int yuyvOffset = yuyvLineStart + lineOffset;
yuyv[yuyvOffset] = i420[i420YIndex];
yuyv[yuyvOffset + 1] = u;
yuyv[yuyvOffset + 2] = i420[i420YIndex + 1];
yuyv[yuyvOffset + 3] = v;
//奇数行数据赋值
int yuyvNextLineOffset = yuyvLineStart + yuyvLineSize + lineOffset;
yuyv[yuyvNextLineOffset] = i420[i420YIndex + width];
yuyv[yuyvNextLineOffset + 1] = u;
yuyv[yuyvNextLineOffset + 2] = i420[i420YIndex + width + 1];
yuyv[yuyvNextLineOffset + 3] = v;
i420YIndex += 2;
}
i420YIndex += width;
yuyvLineStart += (width << 2);
}
return yuyv;
}
4. Обрезка изображения
Как и в случае преобразования формата, обрезка изображения не представляет сложности, если вы знаете, как расположены изображения. В этой статьеRGB
Обрезка изображения в цветовом пространстве иYUV
Обрезка изображения в цветовом пространстве.
1. Обрезать NV21 или NV12
public static byte[] cropYuv420sp(byte[] yuv420sp, int width, int height, int left, int top, int right, int bottom) {
if (yuv420sp == null || yuv420sp.length == 0 || width * height * 3 / 2 != yuv420sp.length) {
throw new IllegalArgumentException("invalid image params!");
}
if (left < 0 || top < 0 || right > width || bottom > height) {
throw new IllegalArgumentException("rect out of bounds!");
}
if (right < left || bottom < top) {
throw new IllegalArgumentException("invalid rect!");
}
if (((right - left) & 1) == 1 || ((bottom - top) & 1) == 1) {
throw new IllegalArgumentException("yuv420sp width and height must be even!");
}
if ((left & 1 )== 1){
throw new IllegalArgumentException("yuv420sp crop left borderIndex and right borderIndex must be even!");
}
int cropImageWidth = right - left;
int cropImageHeight = bottom - top;
byte[] cropYuv420sp = new byte[cropImageWidth * cropImageHeight * 3 / 2];
//复制Y
int originalYLineStart = top * width;
int targetYIndex = 0;
//复制UV
int originalUVLineStart = width * height + top * width / 2;
int targetUVIndex = cropImageWidth * cropImageHeight;
for (int i = top; i < bottom; i++) {
System.arraycopy(yuv420sp, originalYLineStart + left, cropYuv420sp, targetYIndex, cropImageWidth);
originalYLineStart += width;
targetYIndex += cropImageWidth;
if ((i & 1) == 0) {
System.arraycopy(yuv420sp, originalUVLineStart + left, cropYuv420sp, targetUVIndex, cropImageWidth);
originalUVLineStart += width;
targetUVIndex += cropImageWidth;
}
}
return cropYuv420sp;
}
2. Обрезать BGR24
public static byte[] cropBgr24(byte[] bgr24, int width, int height, int left, int top, int right, int bottom) {
if (bgr24 == null || bgr24.length == 0 || width * height * 3 != bgr24.length) {
throw new IllegalArgumentException("invalid image params!");
}
if (left < 0 || top < 0 || right > width || bottom > height) {
throw new IllegalArgumentException("rect out of bounds!");
}
if (right < left || bottom < top) {
throw new IllegalArgumentException("invalid rect!");
}
int cropImageWidth = right - left;
int cropImageHeight = bottom - top;
byte[] cropBgr24 = new byte[cropImageWidth * cropImageHeight * 3];
int originalLineStart = top * width * 3;
int targetIndex = 0;
for (int i = top; i < bottom; i++) {
System.arraycopy(bgr24, originalLineStart + left * 3, cropBgr24, targetIndex, cropImageWidth * 3);
originalLineStart += width * 3;
targetIndex += cropImageWidth * 3;
}
return cropBgr24;
}