ImBuf: Add support for WebP image format

Currently only supports single image frames (no animation possible).

If quality slider is set to 100 then lossless compression will be used,
otherwise lossy compression is used.

Gives about 35% reduction of filesize  save when re-saving splash screens with lossless
compression.
Also saves much faster, up to 15x faster than PNG with a better compression ratio as a plus.

Note, this is currently left disabled until we have WebP libs (see T95206)

For testing precompiled libs can be downloaded from Google:
https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html

Differential Revision: https://developer.blender.org/D1598
This commit is contained in:
Aaron Carlisle 2022-03-24 18:24:06 -04:00
parent 07846b31f3
commit 4fd0a69d7b
20 changed files with 347 additions and 6 deletions

View File

@ -284,6 +284,7 @@ option(WITH_IMAGE_TIFF "Enable LibTIFF Support" ON)
option(WITH_IMAGE_DDS "Enable DDS Image Support" ON)
option(WITH_IMAGE_CINEON "Enable CINEON and DPX Image Support" ON)
option(WITH_IMAGE_HDR "Enable HDR Image Support" ON)
option(WITH_IMAGE_WEBP "Enable WebP Image Support" OFF)
# Audio/Video format support
option(WITH_CODEC_AVI "Enable Blenders own AVI file support (raw/jpeg)" ON)

View File

@ -0,0 +1,77 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright 2022 Blender Foundation.
# - Find WebP library
# Find the native WebP includes and library
# This module defines
# WEBP_INCLUDE_DIRS, where to find WebP headers, Set when WebP is found.
# WEBP_LIBRARIES, libraries to link against to use WebP.
# WEBP_ROOT_DIR, The base directory to search for WebP.
# This can also be an environment variable.
# WEBP_FOUND, If false, do not try to use WebP.
#
# also defined, but not for general use are
# WEBP_LIBRARY, where to find the WEBP library.
# If WEBP_ROOT_DIR was defined in the environment, use it.
IF(NOT WEBP_ROOT_DIR AND NOT $ENV{WEBP_ROOT_DIR} STREQUAL "")
SET(WEBP_ROOT_DIR $ENV{WEBP_ROOT_DIR})
ENDIF()
SET(_webp_SEARCH_DIRS
${WEBP_ROOT_DIR}
/opt/lib/webp
)
FIND_PATH(WEBP_INCLUDE_DIR
NAMES
webp/types.h
HINTS
${_webp_SEARCH_DIRS}
PATH_SUFFIXES
include
)
SET(_webp_FIND_COMPONENTS
webp
webpmux
webpdemux
)
SET(_webp_LIBRARIES)
FOREACH(COMPONENT ${_webp_FIND_COMPONENTS})
STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT)
FIND_LIBRARY(WEBP_${UPPERCOMPONENT}_LIBRARY
NAMES
${COMPONENT}
NAMES_PER_DIR
HINTS
${_webp_SEARCH_DIRS}
PATH_SUFFIXES
lib64 lib lib/static
)
LIST(APPEND _webp_LIBRARIES "${WEBP_${UPPERCOMPONENT}_LIBRARY}")
ENDFOREACH()
IF(${WEBP_WEBP_LIBRARY_NOTFOUND})
set(WEBP_FOUND FALSE)
ELSE()
# handle the QUIETLY and REQUIRED arguments and set WEBP_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(WebP DEFAULT_MSG _webp_LIBRARIES WEBP_INCLUDE_DIR)
IF(WEBP_FOUND)
get_filename_component(WEBP_LIBRARY_DIR ${WEBP_WEBP_LIBRARY} DIRECTORY)
SET(WEBP_INCLUDE_DIRS ${WEBP_INCLUDE_DIR})
SET(WEBP_LIBRARIES ${_webp_LIBRARIES})
ELSE()
SET(WEBPL_PUGIXML_FOUND FALSE)
ENDIF()
ENDIF()
MARK_AS_ADVANCED(
WEBP_INCLUDE_DIR
WEBP_LIBRARY_DIR
)

View File

@ -232,6 +232,15 @@ if(WITH_IMAGE_TIFF)
endif()
endif()
if(WITH_IMAGE_WEBP)
set(WEBP_ROOT_DIR ${LIBDIR}/webp)
find_package(WebP)
if(NOT WEBP_FOUND)
message(WARNING "WebP not found, disabling WITH_IMAGE_WEBP")
set(WITH_IMAGE_WEBP OFF)
endif()
endif()
if(WITH_BOOST)
set(Boost_NO_BOOST_CMAKE ON)
set(BOOST_ROOT ${LIBDIR}/boost)

View File

@ -368,6 +368,14 @@ if(WITH_PUGIXML)
endif()
endif()
if(WITH_IMAGE_WEBP)
set(WEBP_ROOT_DIR ${LIBDIR}/webp)
find_package_wrapper(WebP)
if(NOT WEBP_FOUND)
set(WITH_IMAGE_WEBP OFF)
endif()
endif()
if(WITH_OPENIMAGEIO)
find_package_wrapper(OpenImageIO)
set(OPENIMAGEIO_LIBRARIES

View File

@ -343,6 +343,14 @@ if(WITH_FFTW3)
set(FFTW3_LIBPATH ${FFTW3}/lib)
endif()
windows_find_package(WebP)
if(NOT WEBP_FOUND)
set(WEBP_INCLUDE_DIRS ${LIBDIR}/webp/include)
set(WEBP_ROOT_DIR ${LIBDIR}/webp)
set(WEBP_LIBRARIES ${LIBDIR}/webp/lib/webp.lib ${LIBDIR}/webp/lib/webpdemux.lib ${LIBDIR}/webp/lib/webpmux.lib)
set(WEBP_FOUND ON)
endif()
if(WITH_OPENCOLLADA)
set(OPENCOLLADA ${LIBDIR}/opencollada)

View File

@ -101,6 +101,7 @@ macro(cycles_target_link_libraries target)
${PNG_LIBRARIES}
${JPEG_LIBRARIES}
${TIFF_LIBRARY}
${WEBP_LIBRARIES}
${OPENJPEG_LIBRARIES}
${OPENEXR_LIBRARIES}
${OPENEXR_LIBRARIES} # For circular dependencies between libs.

View File

@ -607,6 +607,10 @@ if(WITH_IMAGE_HDR)
add_definitions(-DWITH_HDR)
endif()
if(WITH_IMAGE_WEBP)
add_definitions(-DWITH_WEBP)
endif()
if(WITH_CODEC_AVI)
list(APPEND INC
../io/avi

View File

@ -120,6 +120,12 @@ int BKE_imtype_to_ftype(const char imtype, ImbFormatOptions *r_options)
return IMB_FTYPE_JP2;
}
#endif
#ifdef WITH_WEBP
if (imtype == R_IMF_IMTYPE_WEBP) {
r_options->quality = 90;
return IMB_FTYPE_WEBP;
}
#endif
r_options->quality = 90;
return IMB_FTYPE_JPG;
@ -177,6 +183,11 @@ char BKE_ftype_to_imtype(const int ftype, const ImbFormatOptions *options)
return R_IMF_IMTYPE_JP2;
}
#endif
#ifdef WITH_WEBP
if (ftype == IMB_FTYPE_WEBP) {
return R_IMF_IMTYPE_WEBP;
}
#endif
return R_IMF_IMTYPE_JPEG90;
}
@ -220,6 +231,7 @@ bool BKE_imtype_supports_quality(const char imtype)
case R_IMF_IMTYPE_JPEG90:
case R_IMF_IMTYPE_JP2:
case R_IMF_IMTYPE_AVIJPEG:
case R_IMF_IMTYPE_WEBP:
return true;
}
return false;
@ -259,6 +271,7 @@ char BKE_imtype_valid_channels(const char imtype, bool write_file)
case R_IMF_IMTYPE_DDS:
case R_IMF_IMTYPE_JP2:
case R_IMF_IMTYPE_DPX:
case R_IMF_IMTYPE_WEBP:
chan_flag |= IMA_CHAN_FLAG_ALPHA;
break;
}
@ -379,6 +392,11 @@ char BKE_imtype_from_arg(const char *imtype_arg)
return R_IMF_IMTYPE_JP2;
}
#endif
#ifdef WITH_WEBP
if (STREQ(imtype_arg, "WEBP")) {
return R_IMF_IMTYPE_WEBP;
}
#endif
return R_IMF_IMTYPE_INVALID;
}
@ -493,6 +511,12 @@ static bool do_add_image_extension(char *string,
}
}
}
#endif
#ifdef WITH_WEBP
else if (imtype == R_IMF_IMTYPE_WEBP) {
if (!BLI_path_extension_check(string, extension_test = ".webp"))
extension = extension_test;
}
#endif
else { // R_IMF_IMTYPE_AVIRAW, R_IMF_IMTYPE_AVIJPEG, R_IMF_IMTYPE_JPEG90 etc
if (!(BLI_path_extension_check_n(string, extension_test = ".jpg", ".jpeg", nullptr))) {
@ -731,6 +755,12 @@ void BKE_image_format_to_imbuf(ImBuf *ibuf, const ImageFormatData *imf)
BLI_assert_msg(0, "Unsupported jp2 codec was specified in im_format->jp2_codec");
}
}
#endif
#ifdef WITH_WEBP
else if (imtype == R_IMF_IMTYPE_WEBP) {
ibuf->ftype = IMB_FTYPE_WEBP;
ibuf->foptions.quality = quality;
}
#endif
else {
/* #R_IMF_IMTYPE_JPEG90, etc. default to JPEG. */
@ -864,6 +894,12 @@ void BKE_image_format_from_imbuf(ImageFormatData *im_format, const ImBuf *imbuf)
}
}
#endif
#ifdef WITH_WEBP
else if (ftype == IMB_FTYPE_WEBP) {
im_format->imtype = R_IMF_IMTYPE_WEBP;
im_format->quality = quality;
}
#endif
else {
im_format->imtype = R_IMF_IMTYPE_JPEG90;

View File

@ -79,6 +79,9 @@ if(WITH_IMAGE_HDR)
add_definitions(-DWITH_HDR)
endif()
if(WITH_IMAGE_WEBP)
add_definitions(-DWITH_WEBP)
endif()
if(WITH_FREESTYLE)
add_definitions(-DWITH_FREESTYLE)

View File

@ -60,6 +60,9 @@ if(WITH_IMAGE_CINEON)
add_definitions(-DWITH_CINEON)
endif()
if(WITH_IMAGE_WEBP)
add_definitions(-DWITH_WEBP)
endif()
blender_add_lib(bf_editor_space_image "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -174,6 +174,19 @@ if(WITH_IMAGE_HDR)
add_definitions(-DWITH_HDR)
endif()
if(WITH_IMAGE_WEBP)
list(APPEND SRC
intern/webp.c
)
list(APPEND INC_SYS
${WEBP_INCLUDE_DIRS}
)
list(APPEND LIB
${WEBP_LIBRARIES}
)
add_definitions(-DWITH_WEBP)
endif()
list(APPEND INC
../../../intern/opencolorio
)

View File

@ -80,6 +80,9 @@ enum eImbFileType {
#ifdef WITH_DDS
IMB_FTYPE_DDS = 13,
#endif
#ifdef WITH_WEBP
IMB_FTYPE_WEBP = 14,
#endif
};
/* Only for readability. */

View File

@ -238,3 +238,16 @@ void imb_loadtiletiff(
bool imb_savetiff(struct ImBuf *ibuf, const char *filepath, int flags);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Format: TIFF (#IMB_FTYPE_WEBP)
* \{ */
bool imb_is_a_webp(const unsigned char *buf, size_t size);
struct ImBuf *imb_loadwebp(const unsigned char *mem,
size_t size,
int flags,
char colorspace[IM_MAX_SPACE]);
bool imb_savewebp(struct ImBuf *ibuf, const char *name, int flags);
/** \} */

View File

@ -196,6 +196,20 @@ const ImFileType IMB_FILE_TYPES[] = {
.filetype = IMB_FTYPE_PSD,
.default_save_role = COLOR_ROLE_DEFAULT_FLOAT,
},
#endif
#ifdef WITH_WEBP
{
.init = NULL,
.exit = NULL,
.is_a = imb_is_a_webp,
.load = imb_loadwebp,
.load_filepath = NULL,
.save = imb_savewebp,
.load_tile = NULL,
.flag = 0,
.filetype = IMB_FTYPE_WEBP,
.default_save_role = COLOR_ROLE_DEFAULT_BYTE,
},
#endif
{NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0},
};

View File

@ -41,12 +41,12 @@
#define UTIL_DEBUG 0
const char *imb_ext_image[] = {
".png", ".tga", ".bmp", ".jpg", ".jpeg", ".sgi", ".rgb", ".rgba",
".png", ".tga", ".bmp", ".jpg", ".jpeg", ".sgi", ".rgb", ".rgba",
#ifdef WITH_TIFF
".tif", ".tiff", ".tx",
".tif", ".tiff", ".tx",
#endif
#ifdef WITH_OPENJPEG
".jp2", ".j2c",
".jp2", ".j2c",
#endif
#ifdef WITH_HDR
".hdr",
@ -55,13 +55,16 @@ const char *imb_ext_image[] = {
".dds",
#endif
#ifdef WITH_CINEON
".dpx", ".cin",
".dpx", ".cin",
#endif
#ifdef WITH_OPENEXR
".exr",
#endif
#ifdef WITH_OPENIMAGEIO
".psd", ".pdd", ".psb",
".psd", ".pdd", ".psb",
#endif
#ifdef WITH_WEBP
".webp",
#endif
NULL,
};

View File

@ -0,0 +1,129 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file blender/imbuf/intern/webp.c
* \ingroup imbuf
*/
#include <stdio.h>
#include <stdlib.h>
#include <webp/decode.h>
#include <webp/encode.h>
#include "BLI_fileops.h"
#include "BLI_utildefines.h"
#include "IMB_colormanagement.h"
#include "IMB_colormanagement_intern.h"
#include "IMB_filetype.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
#include "MEM_guardedalloc.h"
bool imb_is_a_webp(const unsigned char *buf, size_t size)
{
if (WebPGetInfo(buf, size, NULL, NULL)) {
return true;
}
return false;
}
ImBuf *imb_loadwebp(const unsigned char *mem,
size_t size,
int flags,
char colorspace[IM_MAX_SPACE])
{
if (!imb_is_a_webp(mem, size)) {
return NULL;
}
colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_BYTE);
WebPBitstreamFeatures features;
if (WebPGetFeatures(mem, size, &features) != VP8_STATUS_OK) {
fprintf(stderr, "WebP: Failed to parse features\n");
return NULL;
}
const int planes = features.has_alpha ? 32 : 24;
ImBuf *ibuf = IMB_allocImBuf(features.width, features.height, planes, 0);
if (ibuf == NULL) {
fprintf(stderr, "WebP: Failed to allocate image memory\n");
return NULL;
}
if ((flags & IB_test) == 0) {
ibuf->ftype = IMB_FTYPE_WEBP;
imb_addrectImBuf(ibuf);
/* Flip the image during decoding to match Blender. */
unsigned char *last_row = (unsigned char *)(ibuf->rect + (ibuf->y - 1) * ibuf->x);
if (WebPDecodeRGBAInto(mem, size, last_row, (size_t)(ibuf->x) * ibuf->y * 4, -4 * ibuf->x) ==
NULL) {
fprintf(stderr, "WebP: Failed to decode image\n");
}
}
return ibuf;
}
bool imb_savewebp(struct ImBuf *ibuf, const char *name, int UNUSED(flags))
{
const int bytesperpixel = (ibuf->planes + 7) >> 3;
unsigned char *encoded_data, *last_row;
size_t encoded_data_size;
if (bytesperpixel == 3) {
/* We must convert the ImBuf RGBA buffer to RGB as WebP expects a RGB buffer. */
const size_t num_pixels = ibuf->x * ibuf->y;
const uint8_t *rgba_rect = (uint8_t *)ibuf->rect;
uint8_t *rgb_rect = MEM_mallocN(sizeof(uint8_t) * num_pixels * 3, "webp rgb_rect");
for (int i = 0; i < num_pixels; i++) {
rgb_rect[i * 3 + 0] = rgba_rect[i * 4 + 0];
rgb_rect[i * 3 + 1] = rgba_rect[i * 4 + 1];
rgb_rect[i * 3 + 2] = rgba_rect[i * 4 + 2];
}
last_row = (unsigned char *)(rgb_rect + (ibuf->y - 1) * ibuf->x * 3);
MEM_freeN(rgb_rect);
if (ibuf->foptions.quality == 100.0f) {
encoded_data_size = WebPEncodeLosslessRGB(
last_row, ibuf->x, ibuf->y, -3 * ibuf->x, &encoded_data);
}
else {
encoded_data_size = WebPEncodeRGB(
last_row, ibuf->x, ibuf->y, -3 * ibuf->x, ibuf->foptions.quality, &encoded_data);
}
}
else if (bytesperpixel == 4) {
last_row = (unsigned char *)(ibuf->rect + (ibuf->y - 1) * ibuf->x);
if (ibuf->foptions.quality == 100.0f) {
encoded_data_size = WebPEncodeLosslessRGBA(
last_row, ibuf->x, ibuf->y, -4 * ibuf->x, &encoded_data);
}
else {
encoded_data_size = WebPEncodeRGBA(
last_row, ibuf->x, ibuf->y, -4 * ibuf->x, ibuf->foptions.quality, &encoded_data);
}
}
else {
fprintf(stderr, "WebP: Unsupported bytes per pixel: %d for file: '%s'\n", bytesperpixel, name);
return false;
}
if (encoded_data != NULL) {
FILE *fp = BLI_fopen(name, "wb");
if (!fp) {
free(encoded_data);
fprintf(stderr, "WebP: Cannot open file for writing: '%s'\n", name);
return false;
}
fwrite(encoded_data, encoded_data_size, 1, fp);
free(encoded_data);
fclose(fp);
}
return true;
}

View File

@ -465,6 +465,7 @@ typedef struct ImageFormatData {
#define R_IMF_IMTYPE_XVID 32
#define R_IMF_IMTYPE_THEORA 33
#define R_IMF_IMTYPE_PSD 34
#define R_IMF_IMTYPE_WEBP 35
#define R_IMF_IMTYPE_INVALID 255

View File

@ -236,6 +236,10 @@ if(WITH_IMAGE_HDR)
add_definitions(-DWITH_HDR)
endif()
if(WITH_IMAGE_WEBP)
add_definitions(-DWITH_WEBP)
endif()
if(WITH_AUDASPACE)
add_definitions(-DWITH_AUDASPACE)

View File

@ -330,6 +330,13 @@ const EnumPropertyItem rna_enum_curve_fit_method_items[] = {
# define R_IMF_ENUM_TIFF
#endif
#ifdef WITH_WEBP
# define R_IMF_ENUM_WEBP \
{R_IMF_IMTYPE_WEBP, "WEBP", ICON_FILE_IMAGE, "WebP", "Output image in WebP format"},
#else
# define R_IMF_ENUM_WEBP
#endif
#define IMAGE_TYPE_ITEMS_IMAGE_ONLY \
R_IMF_ENUM_BMP \
/* DDS save not supported yet R_IMF_ENUM_DDS */ \
@ -340,7 +347,7 @@ const EnumPropertyItem rna_enum_curve_fit_method_items[] = {
R_IMF_ENUM_TAGA \
R_IMF_ENUM_TAGA_RAW{0, "", 0, " ", NULL}, \
R_IMF_ENUM_CINEON R_IMF_ENUM_DPX R_IMF_ENUM_EXR_MULTILAYER R_IMF_ENUM_EXR R_IMF_ENUM_HDR \
R_IMF_ENUM_TIFF
R_IMF_ENUM_TIFF R_IMF_ENUM_WEBP
#ifdef RNA_RUNTIME
static const EnumPropertyItem image_only_type_items[] = {

View File

@ -224,6 +224,10 @@ if(WITH_IMAGE_TIFF)
add_definitions(-DWITH_TIFF)
endif()
if(WITH_WEBP)
add_definitions(-DWITH_WEBP)
endif()
if(WITH_INPUT_NDOF)
add_definitions(-DWITH_INPUT_NDOF)
endif()