OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include <Python.h> |
| 6 #include <string.h> |
| 7 |
| 8 |
| 9 struct Box { |
| 10 Box() : left(), top(), right(), bottom() {} |
| 11 |
| 12 bool ParseArg(PyObject* obj) { |
| 13 int width; |
| 14 int height; |
| 15 if (!PyArg_ParseTuple(obj, "iiii", &left, &top, &width, &height)) |
| 16 return false; |
| 17 if (left < 0 || top < 0 || width < 0 || height < 0) { |
| 18 PyErr_SetString(PyExc_ValueError, "Box dimensions must be non-negative."); |
| 19 return false; |
| 20 } |
| 21 right = left + width; |
| 22 bottom = top + height; |
| 23 return true; |
| 24 } |
| 25 |
| 26 PyObject* MakeObject() const { |
| 27 if (right <= left || bottom <= top) |
| 28 return Py_None; |
| 29 return Py_BuildValue("iiii", left, top, right - left, bottom - top); |
| 30 } |
| 31 |
| 32 void Union(int x, int y) { |
| 33 if (left > x) left = x; |
| 34 if (right <= x) right = x + 1; |
| 35 if (top > y) top = y; |
| 36 if (bottom <= y) bottom = y + 1; |
| 37 } |
| 38 |
| 39 int width() const { return right - left; } |
| 40 int height() const { return bottom - top; } |
| 41 |
| 42 int left; |
| 43 int top; |
| 44 int right; |
| 45 int bottom; |
| 46 }; |
| 47 |
| 48 |
| 49 // Represents a bitmap buffer with a crop box. |
| 50 struct Bitmap { |
| 51 Bitmap() {} |
| 52 |
| 53 ~Bitmap() { |
| 54 if (pixels.buf) |
| 55 PyBuffer_Release(&pixels); |
| 56 } |
| 57 |
| 58 bool ParseArg(PyObject* obj) { |
| 59 int width; |
| 60 int bpp; |
| 61 PyObject* box_object; |
| 62 if (!PyArg_ParseTuple(obj, "s*iiO", &pixels, &width, &bpp, &box_object)) |
| 63 return false; |
| 64 if (width <= 0 || bpp <= 0) { |
| 65 PyErr_SetString(PyExc_ValueError, "Width and bpp must be positive."); |
| 66 return false; |
| 67 } |
| 68 |
| 69 row_stride = width * bpp; |
| 70 pixel_stride = bpp; |
| 71 total_size = pixels.len; |
| 72 row_size = row_stride; |
| 73 |
| 74 if (pixels.len % row_stride != 0) { |
| 75 PyErr_SetString(PyExc_ValueError, "Length must be a multiple of width " |
| 76 "and bpp."); |
| 77 return false; |
| 78 } |
| 79 |
| 80 if (!box.ParseArg(box_object)) |
| 81 return false; |
| 82 |
| 83 if (box.bottom * row_stride > total_size || |
| 84 box.right * pixel_stride > row_size) { |
| 85 PyErr_SetString(PyExc_ValueError, "Crop box overflows the bitmap."); |
| 86 return false; |
| 87 } |
| 88 |
| 89 total_size = (box.bottom - box.top) * row_stride; |
| 90 row_size = (box.right - box.left) * pixel_stride; |
| 91 data = reinterpret_cast<const unsigned char*>(pixels.buf) + |
| 92 box.top * row_stride + box.left * pixel_stride; |
| 93 return true; |
| 94 } |
| 95 |
| 96 Py_buffer pixels; |
| 97 Box box; |
| 98 // Points at the top-left pixel in |pixels.buf|. |
| 99 const unsigned char* data; |
| 100 // These counts are in bytes. |
| 101 int row_stride; |
| 102 int pixel_stride; |
| 103 int total_size; |
| 104 int row_size; |
| 105 }; |
| 106 |
| 107 |
| 108 static |
| 109 PyObject* Histogram(PyObject* self, PyObject* bmp_object) { |
| 110 Bitmap bmp; |
| 111 if (!bmp.ParseArg(bmp_object)) |
| 112 return NULL; |
| 113 |
| 114 const int kLength = 3 * 256; |
| 115 int counts[kLength] = {}; |
| 116 |
| 117 for (const unsigned char* row = bmp.data; row < bmp.data + bmp.total_size; |
| 118 row += bmp.row_stride) { |
| 119 for (const unsigned char* pixel = row; pixel < row + bmp.row_size; |
| 120 pixel += bmp.pixel_stride) { |
| 121 ++(counts[256 * 0 + pixel[0]]); |
| 122 ++(counts[256 * 1 + pixel[1]]); |
| 123 ++(counts[256 * 2 + pixel[2]]); |
| 124 } |
| 125 } |
| 126 |
| 127 PyObject* list = PyList_New(kLength); |
| 128 if (!list) |
| 129 return NULL; |
| 130 |
| 131 for (int i = 0; i < kLength; ++i) |
| 132 PyList_SetItem(list, i, PyInt_FromLong(counts[i])); |
| 133 |
| 134 return list; |
| 135 } |
| 136 |
| 137 |
| 138 static inline |
| 139 bool PixelsEqual(const unsigned char* pixel1, const unsigned char* pixel2, |
| 140 int tolerance) { |
| 141 // Note: this works for both RGB and RGBA. Alpha channel is ignored. |
| 142 return (abs(pixel1[0] - pixel2[0]) <= tolerance) && |
| 143 (abs(pixel1[1] - pixel2[1]) <= tolerance) && |
| 144 (abs(pixel1[2] - pixel2[2]) <= tolerance); |
| 145 } |
| 146 |
| 147 |
| 148 static inline |
| 149 bool PixelsEqual(const unsigned char* pixel, int color, int tolerance) { |
| 150 unsigned char pixel2[3] = { color >> 16, color >> 8, color }; |
| 151 return PixelsEqual(pixel, pixel2, tolerance); |
| 152 } |
| 153 |
| 154 |
| 155 static |
| 156 PyObject* Equal(PyObject* self, PyObject* args) { |
| 157 PyObject* bmp_obj1; |
| 158 PyObject* bmp_obj2; |
| 159 int tolerance; |
| 160 if (!PyArg_ParseTuple(args, "OOi", &bmp_obj1, &bmp_obj2, &tolerance)) |
| 161 return NULL; |
| 162 |
| 163 Bitmap bmp1, bmp2; |
| 164 if (!bmp1.ParseArg(bmp_obj1) || !bmp2.ParseArg(bmp_obj2)) |
| 165 return NULL; |
| 166 |
| 167 if (bmp1.box.width() != bmp2.box.width() || |
| 168 bmp1.box.height() != bmp2.box.height()) { |
| 169 PyErr_SetString(PyExc_ValueError, "Bitmap dimensions don't match."); |
| 170 return NULL; |
| 171 } |
| 172 |
| 173 bool simple_match = (tolerance == 0) && |
| 174 (bmp1.pixel_stride == 3) && |
| 175 (bmp2.pixel_stride == 3); |
| 176 for (const unsigned char *row1 = bmp1.data, *row2 = bmp2.data; |
| 177 row1 < bmp1.data + bmp1.total_size; |
| 178 row1 += bmp1.row_stride, row2 += bmp2.row_stride) { |
| 179 if (simple_match) { |
| 180 if (memcmp(row1, row2, bmp1.row_size) != 0) |
| 181 return Py_False; |
| 182 continue; |
| 183 } |
| 184 for (const unsigned char *pixel1 = row1, *pixel2 = row2; |
| 185 pixel1 < row1 + bmp1.row_size; |
| 186 pixel1 += bmp1.pixel_stride, pixel2 += bmp2.pixel_stride) { |
| 187 if (!PixelsEqual(pixel1, pixel2, tolerance)) |
| 188 return Py_False; |
| 189 } |
| 190 } |
| 191 |
| 192 return Py_True; |
| 193 } |
| 194 |
| 195 |
| 196 static |
| 197 PyObject* BoundingBox(PyObject* self, PyObject* args) { |
| 198 PyObject* bmp_object; |
| 199 int color; |
| 200 int tolerance; |
| 201 if (!PyArg_ParseTuple(args, "Oii", &bmp_object, &color, &tolerance)) |
| 202 return NULL; |
| 203 |
| 204 Bitmap bmp; |
| 205 if (!bmp.ParseArg(bmp_object)) |
| 206 return NULL; |
| 207 |
| 208 Box box; |
| 209 box.left = bmp.pixels.len; |
| 210 box.top = bmp.pixels.len; |
| 211 box.right = 0; |
| 212 box.bottom = 0; |
| 213 |
| 214 int count = 0; |
| 215 int y = 0; |
| 216 for (const unsigned char* row = bmp.data; row < bmp.data + bmp.total_size; |
| 217 row += bmp.row_stride, ++y) { |
| 218 int x = 0; |
| 219 for (const unsigned char* pixel = row; pixel < row + bmp.row_size; |
| 220 pixel += bmp.pixel_stride, ++x) { |
| 221 if (!PixelsEqual(pixel, color, tolerance)) |
| 222 continue; |
| 223 box.Union(x, y); |
| 224 ++count; |
| 225 } |
| 226 } |
| 227 |
| 228 return Py_BuildValue("Oi", box.MakeObject(), count); |
| 229 } |
| 230 |
| 231 |
| 232 static |
| 233 PyObject* Crop(PyObject* self, PyObject* bmp_object) { |
| 234 Bitmap bmp; |
| 235 if (!bmp.ParseArg(bmp_object)) |
| 236 return NULL; |
| 237 |
| 238 int out_size = bmp.row_size * bmp.box.height(); |
| 239 unsigned char* out = new unsigned char[out_size]; |
| 240 unsigned char* dst = out; |
| 241 for (const unsigned char* row = bmp.data; |
| 242 row < bmp.data + bmp.total_size; |
| 243 row += bmp.row_stride, dst += bmp.row_size) { |
| 244 // No change in pixel_stride, so we can copy whole rows. |
| 245 memcpy(dst, row, bmp.row_size); |
| 246 } |
| 247 |
| 248 PyObject* result = Py_BuildValue("s#", out, out_size); |
| 249 delete[] out; |
| 250 return result; |
| 251 } |
| 252 |
| 253 |
| 254 static PyMethodDef module_methods[] = { |
| 255 {"Histogram", Histogram, METH_O, |
| 256 "Calculates histogram of bitmap colors. Returns a list of 3x256 ints."}, |
| 257 {"Equal", Equal, METH_VARARGS, |
| 258 "Checks if the two bmps are equal."}, |
| 259 {"BoundingBox", BoundingBox, METH_VARARGS, |
| 260 "Calculates bounding box of matching color."}, |
| 261 {"Crop", Crop, METH_O, |
| 262 "Crops the bmp to crop box."}, |
| 263 {NULL, NULL, 0, NULL} /* sentinel */ |
| 264 }; |
| 265 |
| 266 PyMODINIT_FUNC initbitmaptools(void) { |
| 267 Py_InitModule("bitmaptools", module_methods); |
| 268 } |
OLD | NEW |