Мигрируете из другого провайдера? Check out our migration guide
Выходной формат Delta PNG может дать значительную экономию в плане задержки и пропускной способности и особенно полезен в ситуациях, когда задержка и пропускная способность имеют особое значение, например в мобильных приложениях.
Для этого требуется, чтобы пиксели исходного изображения были загружены на клиентский сайт, а затем к исходному изображению был применен Delta PNG для получения итогового изображения.
Например:
778 ×
639 px
409 048 байтов
110 904 байтов
Даже в этом примере, где внимание сосредоточено на волосах (что является наихудшим сценарием для формата Delta PNG), экономия значительна: 73%
Delta PNG — это обычный PNG-файл, который можно прочитать в любой программной библиотеке, поддерживающей чтение PNG. Единственная разница по сравнению с обычным результатом PNG заключается в самих значениях пикселей. Фон кодируется как прозрачный черный 0x00000000, а передний план — как прозрачный белый 0x00FFFFFF. Частично прозрачные пиксели имеют свои фактические значения цвета.
| Тип пикселя | Оригинал | Обычный PNG | Delta PNG | Источник вывода |
|---|---|---|---|---|
| Передний план |
0xFFrrggbb
|
0xFFrrggbb
|
0x00FFFFFF
|
Оригинал |
| Фон |
0xFFrrggbb
|
0x00000000
|
0x00000000
|
Delta PNG |
| Край |
0xFFrrggbb
|
0x80rrggbb
|
0x80rrggbb
|
Delta PNG |
Это означает, что при расшифровке значений пикселей Delta PNG вам необходимо извлечь фактическое значение пикселя из оригинала, если вы встречаете прозрачный белый 0x00FFFFFF. Остальные пиксели имеют те же значения, что и в обычном формате PNG.
Вот пример кода TypeScript для расшифровки формата Delta PNG:
export function decodeDeltaPngInPlace(originalPixels: Uint8Array, deltaPngPixels: Uint8Array): Uint8Array {
const N = originalPixels.length / 4; // Array of RGBA values, div 4 to get number of pixels
for (let i = 0; i < N; i++) {
const i4 = i * 4;
const alpha = deltaPngPixels[i4 + 3]; // JavaScript is RGBA, +3 to get alpha
if (alpha == 0) {
const r = deltaPngPixels[i4]; // JavaScript is RGBA, +0 to get red
if (r == 0xFF) {
// Transparent white => foreground => take values from original
deltaPngPixels[i4] = originalPixels[i4];
deltaPngPixels[i4 + 1] = originalPixels[i4 + 1];
deltaPngPixels[i4 + 2] = originalPixels[i4 + 2];
deltaPngPixels[i4 + 3] = originalPixels[i4 + 3];
} // else transparent black => background => keep values
} // else partially transparent => keep values
}
return deltaPngPixels;
}
Чтобы узнать больше о работе с изображениями и пиксельными данными в JavaScript, см. превосходное Пособие по работе с пикселями с помощью холста в сети разработчиков Mozilla.
Ваша библиотека загрузки изображений должна иметь возможность сохранять значения пикселей даже для полностью прозрачных пикселей, как это обычно и работает.
Однако, если вы используете, например. Python и широко известную библиотеку OpenCV, то вам нужно использовать флаг cv2.IMREAD_UNCHANGED и загрузить изображение следующим образом: cv2.imread(path, cv2.IMREAD_UNCHANGED). В противном случае OpenCV затирает фактические значения полностью прозрачных пикселей.
К сожалению, OpenCV не применяет никакой информации о повороте, хранящейся в изображении, когда вы используете этот флаг. Вот почему мы возвращаем заголовок X-Input-Orientation, чтобы вы могли применить правильную ориентацию к изображению в этой ситуации.
Вот пример кода Python+OpenCV для применения ориентации:
def apply_exif_rotation(im: np.ndarray, orientation: int) -> np.ndarray:
# https://note.nkmk.me/en/python-opencv-numpy-rotate-flip/
if 1 < orientation <= 8:
if 2 == orientation: # TOP-RIGHT, flip left-right, [1, 1] -> [-1, 1]
im = cv2.flip(im, 1)
elif 3 == orientation: # BOTTOM-RIGHT, rotate 180
im = cv2.rotate(im, cv2.ROTATE_180)
elif 4 == orientation: # BOTTOM-LEFT, flip up-down, [1, 1] -> [1, -1]
im = cv2.flip(im, 0)
elif 5 == orientation: # LEFT-TOP, Rotate 90 and flip left-right
im = cv2.rotate(im, cv2.ROTATE_90_CLOCKWISE)
im = cv2.flip(im, 1)
elif 6 == orientation: # RIGHT-TOP, Rotate 90
im = cv2.rotate(im, cv2.ROTATE_90_CLOCKWISE)
elif 7 == orientation: # RIGHT-BOTTOM,
im = cv2.rotate(im, cv2.ROTATE_90_CLOCKWISE)
im = cv2.flip(im, 0)
else: # 8 == orientation: # LEFT-BOTTOM, Rotate 270
im = cv2.rotate(im, cv2.ROTATE_90_COUNTERCLOCKWISE)
return im