Browse Source

Merge branch 'release/1.10.x'

rdb 6 years ago
parent
commit
35135ea0e1

+ 7 - 27
direct/src/p3d/DeploymentTools.py

@@ -344,6 +344,7 @@ class Icon:
             if required_size * 2 in sizes:
                 from_size = required_size * 2
             else:
+                from_size = 0
                 for from_size in sizes:
                     if from_size > required_size:
                         break
@@ -367,7 +368,7 @@ class Icon:
         # XOR mask
         if bpp == 24:
             # Align rows to 4-byte boundary
-            rowalign = '\0' * (-(size * 3) & 3)
+            rowalign = b'\0' * (-(size * 3) & 3)
             for y in xrange(size):
                 for x in xrange(size):
                     r, g, b = image.getXel(x, size - y - 1)
@@ -383,35 +384,14 @@ class Icon:
         elif bpp == 8:
             # We'll have to generate a palette of 256 colors.
             hist = PNMImage.Histogram()
-            if image.hasAlpha():
-                # Make a copy without alpha channel.
-                image2 = PNMImage(image)
+            image2 = PNMImage(image)
+            if image2.hasAlpha():
                 image2.premultiplyAlpha()
                 image2.removeAlpha()
-            else:
-                image2 = image
+            image2.quantize(256)
             image2.make_histogram(hist)
             colors = list(hist.get_pixels())
-            if len(colors) > 256:
-                # Palette too large; remove infrequent colors.
-                colors.sort(key=hist.get_count, reverse=True)
-
-                # Find the closest color on the palette matching each color
-                # that didn't fit.  This is certainly not the best palette
-                # generation code, but it'll do for now.
-                closest_indices = []
-                for color in colors[256:]:
-                    closest_index = 0
-                    closest_diff = 1025
-                    for i, closest_color in enumerate(colors[:256]):
-                        diff = abs(color.get_red() - closest_color.get_red()) \
-                             + abs(color.get_green() - closest_color.get_green()) \
-                             + abs(color.get_blue() - closest_color.get_blue())
-                        if diff < closest_diff:
-                            closest_index = i
-                            closest_diff = diff
-                    assert closest_diff < 100
-                    closest_indices.append(closest_index)
+            assert len(colors) <= 256
 
             # Write the palette.
             i = 0
@@ -503,7 +483,7 @@ class Icon:
             if size > 256:
                 continue
             elif size == 256:
-                ico.write('\0\0')
+                ico.write(b'\0\0')
             else:
                 ico.write(struct.pack('<BB', size, size))
 

+ 91 - 77
panda/src/pnmimage/pnmImage.I

@@ -71,6 +71,57 @@ clamp_val(int input_value) const {
   return (xelval)std::min(std::max(0, input_value), (int)get_maxval());
 }
 
+/**
+ * A handy function to scale non-alpha values from [0..1] to
+ * [0..get_maxval()].  Do not use this for alpha values, see to_alpha_val.
+ */
+INLINE xel PNMImage::
+to_val(const LRGBColorf &value) const {
+  xel col;
+  switch (_xel_encoding) {
+  case XE_generic:
+  case XE_generic_alpha:
+    {
+      LRGBColorf scaled = value * get_maxval() + 0.5f;
+      col.r = clamp_val((int)scaled[0]);
+      col.g = clamp_val((int)scaled[1]);
+      col.b = clamp_val((int)scaled[2]);
+    }
+    break;
+
+  case XE_generic_sRGB:
+  case XE_generic_sRGB_alpha:
+    col.r = clamp_val((int)
+      (encode_sRGB_float(value[0]) * get_maxval() + 0.5f));
+    col.g = clamp_val((int)
+      (encode_sRGB_float(value[1]) * get_maxval() + 0.5f));
+    col.b = clamp_val((int)
+      (encode_sRGB_float(value[2]) * get_maxval() + 0.5f));
+    break;
+
+  case XE_uchar_sRGB:
+  case XE_uchar_sRGB_alpha:
+    encode_sRGB_uchar(LColorf(value, 0.0f), col);
+    break;
+
+  case XE_uchar_sRGB_sse2:
+  case XE_uchar_sRGB_alpha_sse2:
+    encode_sRGB_uchar_sse2(LColorf(value, 0.0f), col);
+    break;
+
+  case XE_scRGB:
+  case XE_scRGB_alpha:
+    {
+      LRGBColorf scaled = value * 8192.f + 4096.5f;
+      col.r = std::min(std::max(0, (int)scaled[0]), 65535);
+      col.g = std::min(std::max(0, (int)scaled[1]), 65535);
+      col.b = std::min(std::max(0, (int)scaled[2]), 65535);
+    }
+    break;
+  }
+  return col;
+}
+
 /**
  * A handy function to scale non-alpha values from [0..1] to
  * [0..get_maxval()].  Do not use this for alpha values, see to_alpha_val.
@@ -112,6 +163,44 @@ to_alpha_val(float input_value) const {
   return clamp_val((int)(input_value * get_maxval() + 0.5));
 }
 
+/**
+ * A handy function to scale non-alpha values from [0..get_maxval()] to
+ * [0..1].  Do not use this for alpha values, see from_alpha_val.
+ */
+INLINE LRGBColorf PNMImage::
+from_val(const xel &col) const {
+  switch (_xel_encoding) {
+  case XE_generic:
+  case XE_generic_alpha:
+    return LRGBColorf(col.r, col.g, col.b) * _inv_maxval;
+
+  case XE_generic_sRGB:
+  case XE_generic_sRGB_alpha:
+    return LRGBColorf(
+      decode_sRGB_float(col.r * _inv_maxval),
+      decode_sRGB_float(col.g * _inv_maxval),
+      decode_sRGB_float(col.b * _inv_maxval));
+
+  case XE_uchar_sRGB:
+  case XE_uchar_sRGB_alpha:
+  case XE_uchar_sRGB_sse2:
+  case XE_uchar_sRGB_alpha_sse2:
+    return LRGBColorf(
+      decode_sRGB_float((unsigned char)col.r),
+      decode_sRGB_float((unsigned char)col.g),
+      decode_sRGB_float((unsigned char)col.b));
+
+  case XE_scRGB:
+  case XE_scRGB_alpha:
+    return LRGBColorf((int)col.r - 4096,
+                      (int)col.g - 4096,
+                      (int)col.b - 4096) * (1.f / 8192.f);
+
+  default:
+    return LRGBColorf(0);
+  }
+}
+
 /**
  * A handy function to scale non-alpha values from [0..get_maxval()] to
  * [0..1].  Do not use this for alpha values, see from_alpha_val.
@@ -479,39 +568,7 @@ set_alpha_val(int x, int y, xelval a) {
 INLINE LRGBColorf PNMImage::
 get_xel(int x, int y) const {
   nassertr(x >= 0 && x < _x_size && y >= 0 && y < _y_size, LRGBColorf::zero());
-
-  const xel &col = row(y)[x];
-
-  switch (_xel_encoding) {
-  case XE_generic:
-  case XE_generic_alpha:
-    return LRGBColorf(col.r, col.g, col.b) * _inv_maxval;
-
-  case XE_generic_sRGB:
-  case XE_generic_sRGB_alpha:
-    return LRGBColorf(
-      decode_sRGB_float(col.r * _inv_maxval),
-      decode_sRGB_float(col.g * _inv_maxval),
-      decode_sRGB_float(col.b * _inv_maxval));
-
-  case XE_uchar_sRGB:
-  case XE_uchar_sRGB_alpha:
-  case XE_uchar_sRGB_sse2:
-  case XE_uchar_sRGB_alpha_sse2:
-    return LRGBColorf(
-      decode_sRGB_float((unsigned char)col.r),
-      decode_sRGB_float((unsigned char)col.g),
-      decode_sRGB_float((unsigned char)col.b));
-
-  case XE_scRGB:
-  case XE_scRGB_alpha:
-    return LRGBColorf((int)col.r - 4096,
-                      (int)col.g - 4096,
-                      (int)col.b - 4096) * (1.f / 8192.f);
-
-  default:
-    return LRGBColorf(0);
-  }
+  return from_val(row(y)[x]);
 }
 
 /**
@@ -521,50 +578,7 @@ get_xel(int x, int y) const {
 INLINE void PNMImage::
 set_xel(int x, int y, const LRGBColorf &value) {
   nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size);
-
-  xel &col = row(y)[x];
-
-  switch (_xel_encoding) {
-  case XE_generic:
-  case XE_generic_alpha:
-    {
-      LRGBColorf scaled = value * get_maxval() + 0.5f;
-      col.r = clamp_val((int)scaled[0]);
-      col.g = clamp_val((int)scaled[1]);
-      col.b = clamp_val((int)scaled[2]);
-    }
-    break;
-
-  case XE_generic_sRGB:
-  case XE_generic_sRGB_alpha:
-    col.r = clamp_val((int)
-      (encode_sRGB_float(value[0]) * get_maxval() + 0.5f));
-    col.g = clamp_val((int)
-      (encode_sRGB_float(value[1]) * get_maxval() + 0.5f));
-    col.b = clamp_val((int)
-      (encode_sRGB_float(value[2]) * get_maxval() + 0.5f));
-    break;
-
-  case XE_uchar_sRGB:
-  case XE_uchar_sRGB_alpha:
-    encode_sRGB_uchar(LColorf(value, 0.0f), col);
-    break;
-
-  case XE_uchar_sRGB_sse2:
-  case XE_uchar_sRGB_alpha_sse2:
-    encode_sRGB_uchar_sse2(LColorf(value, 0.0f), col);
-    break;
-
-  case XE_scRGB:
-  case XE_scRGB_alpha:
-    {
-      LRGBColorf scaled = value * 8192.f + 4096.5f;
-      col.r = std::min(std::max(0, (int)scaled[0]), 65535);
-      col.g = std::min(std::max(0, (int)scaled[1]), 65535);
-      col.b = std::min(std::max(0, (int)scaled[2]), 65535);
-    }
-    break;
-  }
+  row(y)[x] = to_val(value);
 }
 
 /**

+ 131 - 0
panda/src/pnmimage/pnmImage.cxx

@@ -1928,6 +1928,51 @@ make_histogram(PNMImage::Histogram &histogram) {
   histogram.swap(pixels, hist_map);
 }
 
+/**
+ * Reduces the number of unique colors in the image to (at most) the given
+ * count.  Fewer colors than requested may be left in the image after this
+ * operation, but never more.
+ *
+ * At present, this is only supported on images without an alpha channel.
+ *
+ * @since 1.10.5
+ */
+void PNMImage::
+quantize(size_t max_colors) {
+  nassertv(_array != nullptr);
+  nassertv(!has_alpha());
+  size_t array_size = _x_size * _y_size;
+
+  // Get all the unique colors in this image.
+  pmap<xel, xel> color_map;
+  for (size_t i = 0; i < array_size; ++i) {
+    color_map[_array[i]];
+  }
+
+  size_t num_colors = color_map.size();
+  if (num_colors <= max_colors) {
+    // We are already down to the requested number of colors.
+    return;
+  }
+
+  // Collect all the colors into a contiguous array.
+  xel *colors = (xel *)alloca(num_colors * sizeof(xel));
+  size_t i = 0;
+  for (pmap<xel, xel>::const_iterator it = color_map.begin();
+       it != color_map.end(); ++it) {
+    colors[i++] = it->first;
+  }
+  nassertv(i == num_colors);
+
+  // Apply the median cut algorithm, which will give us a color map.
+  r_quantize(color_map, max_colors, colors, num_colors);
+
+  // Replace all the existing colors with the corresponding bucket average.
+  for (size_t i = 0; i < array_size; ++i) {
+    _array[i] = color_map[_array[i]];
+  }
+}
+
 /**
  * Fills the image with a grayscale perlin noise pattern based on the
  * indicated parameters.  Uses set_xel to set the grayscale values.  The sx
@@ -2161,6 +2206,92 @@ setup_encoding() {
   }
 }
 
+/**
+ * Recursive implementation of quantize() using the median cut algorithm.
+ */
+void PNMImage::
+r_quantize(pmap<xel, xel> &color_map, size_t max_colors,
+           xel *colors, size_t num_colors) {
+  if (num_colors <= max_colors) {
+    // All points in this bucket can be preserved 1:1.
+    for (size_t i = 0; i < num_colors; ++i) {
+      const xel &col = colors[i];
+      color_map[col] = col;
+    }
+    return;
+  }
+  else if (max_colors == 1) {
+    // We've reached the target.  Calculate the average, in linear space.
+    LRGBColorf avg(0);
+    for (size_t i = 0; i < num_colors; ++i) {
+      avg += from_val(colors[i]);
+    }
+    avg *= 1.0f / num_colors;
+    xel avg_val = to_val(avg);
+
+    // Map all colors in this bucket to the avg.
+    for (size_t i = 0; i < num_colors; ++i) {
+      color_map[colors[i]] = avg_val;
+    }
+    return;
+  }
+  else if (max_colors == 0) {
+    // Not sure how this happens, but we can't preserve any color here.
+    return;
+  }
+
+  // Find the minimum/maximum RGB values.  We should probably do this in
+  // linear space, but eh.
+  xelval min_r = _maxval;
+  xelval min_g = _maxval;
+  xelval min_b = _maxval;
+  xelval max_r = 0, max_g = 0, max_b = 0;
+  for (size_t i = 0; i < num_colors; ++i) {
+    const xel &col = colors[i];
+    min_r = std::min(min_r, col.r);
+    max_r = std::max(max_r, col.r);
+    min_g = std::min(min_g, col.g);
+    max_g = std::max(max_g, col.g);
+    min_b = std::min(min_b, col.b);
+    max_b = std::max(max_b, col.b);
+  }
+
+  int diff_r = max_r - min_r;
+  int diff_g = max_g - min_g;
+  int diff_b = max_b - min_b;
+
+  auto sort_by_red = [](const xel &c1, const xel &c2) {
+    return c1.r < c2.r;
+  };
+  auto sort_by_green = [](const xel &c1, const xel &c2) {
+    return c1.g < c2.g;
+  };
+  auto sort_by_blue = [](const xel &c1, const xel &c2) {
+    return c1.b < c2.b;
+  };
+
+  // Sort by the component with the most variation.
+  if (diff_g >= diff_r) {
+    if (diff_g >= diff_b) {
+      std::sort(colors, colors + num_colors, sort_by_green);
+    } else {
+      std::sort(colors, colors + num_colors, sort_by_blue);
+    }
+  } else if (diff_r >= diff_b) {
+    std::sort(colors, colors + num_colors, sort_by_red);
+  } else {
+    std::sort(colors, colors + num_colors, sort_by_blue);
+  }
+
+  // Subdivide the sorted colors into two buckets, and recurse.
+  size_t max_colors_1 = max_colors / 2;
+  size_t max_colors_2 = max_colors - max_colors_1;
+  size_t num_colors_1 = num_colors / 2;
+  size_t num_colors_2 = num_colors - num_colors_1;
+  r_quantize(color_map, max_colors_1, colors, num_colors_1);
+  r_quantize(color_map, max_colors_2, colors + num_colors_1, num_colors_2);
+}
+
 /**
  * Recursively fills in the minimum distance measured from a certain set of
  * points into the gray channel.

+ 6 - 0
panda/src/pnmimage/pnmImage.h

@@ -68,8 +68,10 @@ PUBLISHED:
   INLINE ~PNMImage();
 
   INLINE xelval clamp_val(int input_value) const;
+  INLINE xel to_val(const LRGBColorf &input_value) const;
   INLINE xelval to_val(float input_value) const;
   INLINE xelval to_alpha_val(float input_value) const;
+  INLINE LRGBColorf from_val(const xel &input_value) const;
   INLINE float from_val(xelval input_value) const;
   INLINE float from_alpha_val(xelval input_value) const;
 
@@ -254,6 +256,7 @@ PUBLISHED:
                                   int xborder = 0, int yborder = 0);
 
   void make_histogram(Histogram &hist);
+  void quantize(size_t max_colors);
   BLOCKING void perlin_noise_fill(float sx, float sy, int table_size = 256,
                                   unsigned long seed = 0);
   void perlin_noise_fill(StackedPerlinNoise2 &perlin);
@@ -346,6 +349,9 @@ private:
   void setup_rc();
   void setup_encoding();
 
+  void r_quantize(pmap<xel, xel> &color_map, size_t max_colors,
+                  xel *colors, size_t num_colors);
+
 PUBLISHED:
   PNMImage operator ~() const;
 

+ 16 - 0
panda/src/pnmimage/pnmimage_base.h

@@ -59,6 +59,22 @@ PUBLISHED:
   void operator *= (const double mult)
     { r *= mult; g *= mult; b *= mult; }
 
+  bool operator == (const pixel &other) {
+    return r == other.r && g == other.g && r == other.r;
+  }
+  bool operator != (const pixel &other) {
+    return r != other.r || g != other.g || r != other.r;
+  }
+  bool operator < (const pixel &other) const {
+    if (r != other.r) {
+      return r < other.r;
+    }
+    if (g != other.g) {
+      return g < other.g;
+    }
+    return b < other.b;
+  }
+
 #ifdef HAVE_PYTHON
   static int size() { return 3; }
   void output(std::ostream &out) {

+ 1 - 0
pandatool/src/assimp/config_assimp.h

@@ -15,6 +15,7 @@
 #define CONFIG_ASSIMP_H
 
 #include "pandatoolbase.h"
+#include "notifyCategoryProxy.h"
 #include "configVariableBool.h"
 #include "configVariableDouble.h"
 #include "dconfig.h"

+ 2 - 3
pandatool/src/miscprogs/binToC.cxx

@@ -99,8 +99,7 @@ run() {
   out << std::hex << std::setfill('0');
   int count = 0;
   int col = 0;
-  unsigned int ch;
-  ch = in.get();
+  int ch = in.get();
   while (!in.fail() && ch != EOF) {
     if (col == 0) {
       out << "\n  ";
@@ -110,7 +109,7 @@ run() {
     } else {
       out << ", ";
     }
-    out << "0x" << std::setw(2) << ch;
+    out << "0x" << std::setw(2) << (unsigned int)ch;
     col++;
     count++;
     ch = in.get();

+ 1 - 1
pandatool/src/ptloader/config_ptloader.h

@@ -15,7 +15,7 @@
 #define CONFIG_PTLOADER_H
 
 #include "pandatoolbase.h"
-
+#include "notifyCategoryProxy.h"
 #include "dconfig.h"
 #include "distanceUnit.h"
 #include "configVariableEnum.h"

+ 51 - 0
tests/pnmimage/test_pnmimage.py

@@ -1,4 +1,5 @@
 from panda3d.core import PNMImage, PNMImageHeader
+from random import randint
 
 
 def test_pixelspec_ctor():
@@ -19,3 +20,53 @@ def test_pixelspec_coerce():
     img = PNMImage(1, 1, 4)
     img.set_pixel(0, 0, (1, 2, 3, 4))
     assert img.get_pixel(0, 0) == (1, 2, 3, 4)
+
+
+def test_pnmimage_to_val():
+    img = PNMImage(1, 1)
+    assert img.to_val(-0.5) == 0
+    assert img.to_val(0.0) == 0
+    assert img.to_val(0.5) == 128
+    assert img.to_val(1.0) == 255
+    assert img.to_val(2.0) == 255
+
+
+def test_pnmimage_from_val():
+    img = PNMImage(1, 1)
+    assert img.from_val(0) == 0.0
+    assert img.to_val(img.from_val(128)) == 128
+    assert img.from_val(255) == 1.0
+
+
+def test_pnmimage_quantize():
+    img = PNMImage(32, 32, 3)
+
+    for x in range(32):
+        for y in range(32):
+            img.set_xel_val(x, y, randint(0, 100), randint(50, 100), randint(0, 1))
+
+    hist = PNMImage.Histogram()
+    img.make_histogram(hist)
+    num_colors = hist.get_num_pixels()
+    assert num_colors > 100
+
+    img2 = PNMImage(img)
+    img2.quantize(100)
+    hist = PNMImage.Histogram()
+    img2.make_histogram(hist)
+    assert hist.get_num_pixels() <= 100
+
+    # Make sure that this is reasonably close
+    max_dist = 0
+    for x in range(32):
+        for y in range(32):
+            diff = img.get_xel(x, y) - img2.get_xel(x, y)
+            max_dist = max(max_dist, diff.length_squared())
+
+            # Also make sure that they are not out of range of the original
+            col = img2.get_xel_val(x, y)
+            assert col.r <= 100
+            assert col.g >= 50 and col.g <= 100
+            assert col.b in (0, 1)
+
+    assert max_dist < 0.1 ** 2

+ 35 - 0
tests/putil/test_modifierbuttons.py

@@ -0,0 +1,35 @@
+from panda3d.core import ModifierButtons
+
+
+def test_modifierbuttons_empty():
+    # Tests the initial state of a ModifierButtons object.
+    btns = ModifierButtons()
+    assert btns == ModifierButtons(btns)
+    assert btns != ModifierButtons()
+    assert btns.matches(ModifierButtons())
+    assert not btns.is_down("alt")
+    assert not btns.is_any_down()
+    assert not btns.has_button("alt")
+    assert btns.get_prefix() == ""
+    assert btns.get_num_buttons() == 0
+    assert len(btns.buttons) == 0
+
+
+def test_modifierbuttons_cow():
+    # Tests the copy-on-write mechanism of the button list.
+    btns1 = ModifierButtons()
+    btns1.add_button("space")
+
+    # Modifying original should not affect copy
+    btns2 = ModifierButtons(btns1)
+    assert tuple(btns2.buttons) == tuple(btns1.buttons)
+    btns1.add_button("enter")
+    assert tuple(btns1.buttons) == ("space", "enter")
+    assert tuple(btns2.buttons) == ("space",)
+
+    # Modifying copy should not affect original
+    btns3 = ModifierButtons(btns2)
+    assert tuple(btns3.buttons) == tuple(btns2.buttons)
+    btns3.add_button("escape")
+    assert tuple(btns2.buttons) == ("space",)
+    assert tuple(btns3.buttons) == ("space", "escape")