libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

gradient_test.cc (7253B)


      1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
      2 //
      3 // Use of this source code is governed by a BSD-style
      4 // license that can be found in the LICENSE file.
      5 
      6 #include <jxl/cms.h>
      7 #include <math.h>
      8 #include <stddef.h>
      9 #include <stdint.h>
     10 
     11 #include <algorithm>
     12 #include <cmath>
     13 #include <utility>
     14 #include <vector>
     15 
     16 #include "lib/jxl/base/common.h"
     17 #include "lib/jxl/base/compiler_specific.h"
     18 #include "lib/jxl/base/data_parallel.h"
     19 #include "lib/jxl/base/span.h"
     20 #include "lib/jxl/codec_in_out.h"
     21 #include "lib/jxl/color_encoding_internal.h"
     22 #include "lib/jxl/enc_params.h"
     23 #include "lib/jxl/image.h"
     24 #include "lib/jxl/image_bundle.h"
     25 #include "lib/jxl/image_ops.h"
     26 #include "lib/jxl/test_utils.h"
     27 #include "lib/jxl/testing.h"
     28 
     29 namespace jxl {
     30 
     31 struct AuxOut;
     32 
     33 namespace {
     34 
     35 // Returns distance of point p to line p0..p1, the result is signed and is not
     36 // normalized.
     37 double PointLineDist(double x0, double y0, double x1, double y1, double x,
     38                      double y) {
     39   return (y1 - y0) * x - (x1 - x0) * y + x1 * y0 - y1 * x0;
     40 }
     41 
     42 // Generates a test image with a gradient from one color to another.
     43 // Angle in degrees, colors can be given in hex as 0xRRGGBB. The angle is the
     44 // angle in which the change direction happens.
     45 Image3F GenerateTestGradient(uint32_t color0, uint32_t color1, double angle,
     46                              size_t xsize, size_t ysize) {
     47   JXL_ASSIGN_OR_DIE(Image3F image, Image3F::Create(xsize, ysize));
     48 
     49   double x0 = xsize / 2.0;
     50   double y0 = ysize / 2.0;
     51   double x1 = x0 + std::sin(angle / 360.0 * 2.0 * kPi);
     52   double y1 = y0 + std::cos(angle / 360.0 * 2.0 * kPi);
     53 
     54   double maxdist =
     55       std::max<double>(fabs(PointLineDist(x0, y0, x1, y1, 0, 0)),
     56                        fabs(PointLineDist(x0, y0, x1, y1, xsize, 0)));
     57 
     58   for (size_t c = 0; c < 3; ++c) {
     59     float c0 = ((color0 >> (8 * (2 - c))) & 255);
     60     float c1 = ((color1 >> (8 * (2 - c))) & 255);
     61     for (size_t y = 0; y < ysize; ++y) {
     62       float* row = image.PlaneRow(c, y);
     63       for (size_t x = 0; x < xsize; ++x) {
     64         double dist = PointLineDist(x0, y0, x1, y1, x, y);
     65         double v = ((dist / maxdist) + 1.0) / 2.0;
     66         float color = c0 * (1.0 - v) + c1 * v;
     67         row[x] = color;
     68       }
     69     }
     70   }
     71 
     72   return image;
     73 }
     74 
     75 // Computes the max of the horizontal and vertical second derivative for each
     76 // pixel, where second derivative means absolute value of difference of left
     77 // delta and right delta (top/bottom for vertical direction).
     78 // The radius over which the derivative is computed is only 1 pixel and it only
     79 // checks two angles (hor and ver), but this approximation works well enough.
     80 Image3F Gradient2(const Image3F& image) {
     81   size_t xsize = image.xsize();
     82   size_t ysize = image.ysize();
     83   JXL_ASSIGN_OR_DIE(Image3F image2, Image3F::Create(xsize, ysize));
     84   for (size_t c = 0; c < 3; ++c) {
     85     for (size_t y = 1; y + 1 < ysize; y++) {
     86       const auto* JXL_RESTRICT row0 = image.ConstPlaneRow(c, y - 1);
     87       const auto* JXL_RESTRICT row1 = image.ConstPlaneRow(c, y);
     88       const auto* JXL_RESTRICT row2 = image.ConstPlaneRow(c, y + 1);
     89       auto* row_out = image2.PlaneRow(c, y);
     90       for (size_t x = 1; x + 1 < xsize; x++) {
     91         float ddx = (row1[x] - row1[x - 1]) - (row1[x + 1] - row1[x]);
     92         float ddy = (row1[x] - row0[x]) - (row2[x] - row1[x]);
     93         row_out[x] = std::max(fabsf(ddx), fabsf(ddy));
     94       }
     95     }
     96     // Copy to the borders
     97     if (ysize > 2) {
     98       auto* JXL_RESTRICT row0 = image2.PlaneRow(c, 0);
     99       const auto* JXL_RESTRICT row1 = image2.PlaneRow(c, 1);
    100       const auto* JXL_RESTRICT row2 = image2.PlaneRow(c, ysize - 2);
    101       auto* JXL_RESTRICT row3 = image2.PlaneRow(c, ysize - 1);
    102       for (size_t x = 1; x + 1 < xsize; x++) {
    103         row0[x] = row1[x];
    104         row3[x] = row2[x];
    105       }
    106     } else {
    107       const auto* row0_in = image.ConstPlaneRow(c, 0);
    108       const auto* row1_in = image.ConstPlaneRow(c, ysize - 1);
    109       auto* row0_out = image2.PlaneRow(c, 0);
    110       auto* row1_out = image2.PlaneRow(c, ysize - 1);
    111       for (size_t x = 1; x + 1 < xsize; x++) {
    112         // Image too narrow, take first derivative instead
    113         row0_out[x] = row1_out[x] = fabsf(row0_in[x] - row1_in[x]);
    114       }
    115     }
    116     if (xsize > 2) {
    117       for (size_t y = 0; y < ysize; y++) {
    118         auto* row = image2.PlaneRow(c, y);
    119         row[0] = row[1];
    120         row[xsize - 1] = row[xsize - 2];
    121       }
    122     } else {
    123       for (size_t y = 0; y < ysize; y++) {
    124         const auto* JXL_RESTRICT row_in = image.ConstPlaneRow(c, y);
    125         auto* row_out = image2.PlaneRow(c, y);
    126         // Image too narrow, take first derivative instead
    127         row_out[0] = row_out[xsize - 1] = fabsf(row_in[0] - row_in[xsize - 1]);
    128       }
    129     }
    130   }
    131   return image2;
    132 }
    133 
    134 /*
    135 Tests if roundtrip with jxl on a gradient image doesn't cause banding.
    136 Only tests if use_gradient is true. Set to false for debugging to see the
    137 distance values.
    138 Angle in degrees, colors can be given in hex as 0xRRGGBB.
    139 */
    140 void TestGradient(ThreadPool* pool, uint32_t color0, uint32_t color1,
    141                   size_t xsize, size_t ysize, float angle, bool fast_mode,
    142                   float butteraugli_distance, bool use_gradient = true) {
    143   CompressParams cparams;
    144   cparams.butteraugli_distance = butteraugli_distance;
    145   if (fast_mode) {
    146     cparams.speed_tier = SpeedTier::kSquirrel;
    147   }
    148   Image3F gradient = GenerateTestGradient(color0, color1, angle, xsize, ysize);
    149 
    150   CodecInOut io;
    151   io.metadata.m.SetUintSamples(8);
    152   io.metadata.m.color_encoding = ColorEncoding::SRGB();
    153   io.SetFromImage(std::move(gradient), io.metadata.m.color_encoding);
    154 
    155   CodecInOut io2;
    156 
    157   std::vector<uint8_t> compressed;
    158   EXPECT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool));
    159   EXPECT_TRUE(test::DecodeFile({}, Bytes(compressed), &io2, pool));
    160   EXPECT_TRUE(io2.Main().TransformTo(io2.metadata.m.color_encoding,
    161                                      *JxlGetDefaultCms(), pool));
    162 
    163   if (use_gradient) {
    164     // Test that the gradient map worked. For that, we take a second derivative
    165     // of the image with Gradient2 to measure how linear the change is in x and
    166     // y direction. For a well handled gradient, we expect max values around
    167     // 0.1, while if there is noticeable banding, which means the gradient map
    168     // failed, the values are around 0.5-1.0 (regardless of
    169     // butteraugli_distance).
    170     Image3F gradient2 = Gradient2(*io2.Main().color());
    171 
    172     // TODO(jyrki): These values used to work with 0.2, 0.2, 0.2.
    173     float image_min;
    174     float image_max;
    175     ImageMinMax(gradient2.Plane(0), &image_min, &image_max);
    176     EXPECT_LE(image_max, 3.15);
    177     ImageMinMax(gradient2.Plane(1), &image_min, &image_max);
    178     EXPECT_LE(image_max, 1.72);
    179     ImageMinMax(gradient2.Plane(2), &image_min, &image_max);
    180     EXPECT_LE(image_max, 5.05);
    181   }
    182 }
    183 
    184 constexpr bool fast_mode = true;
    185 
    186 TEST(GradientTest, SteepGradient) {
    187   test::ThreadPoolForTests pool(8);
    188   // Relatively steep gradients, colors from the sky of stp.png
    189   TestGradient(&pool, 0xd99d58, 0x889ab1, 512, 512, 90, fast_mode, 3.0);
    190 }
    191 
    192 TEST(GradientTest, SubtleGradient) {
    193   test::ThreadPoolForTests pool(8);
    194   // Very subtle gradient
    195   TestGradient(&pool, 0xb89b7b, 0xa89b8d, 512, 512, 90, fast_mode, 4.0);
    196 }
    197 
    198 }  // namespace
    199 }  // namespace jxl