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 <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8
9#if defined(WIN32)
10#include <fcntl.h>
11#include <io.h>
12#endif
13
14enum Commands {
15  CROP_PIXELS = 0,
16  HISTOGRAM = 1,
17  BOUNDING_BOX = 2
18};
19
20bool ReadInt(int* out) {
21  return fread(out, sizeof(*out), 1, stdin) == 1;
22}
23
24void WriteResponse(void* data, int size) {
25  fwrite(&size, sizeof(size), 1, stdout);
26  fwrite(data, size, 1, stdout);
27  fflush(stdout);
28}
29
30struct Box {
31  Box() : left(), top(), right(), bottom() {}
32
33  // Expected input is:
34  // left, top, width, height
35  bool Read() {
36    int width;
37    int height;
38    if (!(ReadInt(&left) && ReadInt(&top) &&
39          ReadInt(&width) && ReadInt(&height))) {
40      fprintf(stderr, "Could not parse Box.\n");
41      return false;
42    }
43    if (left < 0 || top < 0 || width < 0 || height < 0) {
44      fprintf(stderr, "Box dimensions must be non-negative.\n");
45      return false;
46    }
47    right = left + width;
48    bottom = top + height;
49    return true;
50  }
51
52  void Union(int x, int y) {
53    if (left > x) left = x;
54    if (right <= x) right = x + 1;
55    if (top > y) top = y;
56    if (bottom <= y) bottom = y + 1;
57  }
58
59  int width() const { return right - left; }
60  int height() const { return bottom - top; }
61
62  int left;
63  int top;
64  int right;
65  int bottom;
66};
67
68
69// Represents a bitmap buffer with a crop box.
70struct Bitmap {
71  Bitmap() : pixels(NULL) {}
72
73  ~Bitmap() {
74    if (pixels)
75      delete[] pixels;
76  }
77
78  // Expected input is:
79  // bpp, width, height, box, pixels
80  bool Read() {
81    int bpp;
82    int width;
83    int height;
84    if (!(ReadInt(&bpp) && ReadInt(&width) && ReadInt(&height))) {
85      fprintf(stderr, "Could not parse Bitmap initializer.\n");
86      return false;
87    }
88    if (bpp <= 0 || width <= 0 || height <= 0) {
89      fprintf(stderr, "Dimensions must be positive.\n");
90      return false;
91    }
92
93    int size = width * height * bpp;
94
95    row_stride = width * bpp;
96    pixel_stride = bpp;
97    total_size = size;
98    row_size = row_stride;
99
100    if (!box.Read()) {
101      fprintf(stderr, "Expected crop box argument not found.\n");
102      return false;
103    }
104
105    if (box.bottom * row_stride > total_size ||
106        box.right * pixel_stride > row_size) {
107      fprintf(stderr, "Crop box overflows the bitmap.\n");
108      return false;
109    }
110
111    pixels = new unsigned char[size];
112    if (fread(pixels, sizeof(pixels[0]), size, stdin) <
113        static_cast<size_t>(size)) {
114      fprintf(stderr, "Not enough pixels found,\n");
115      return false;
116    }
117
118    total_size = (box.bottom - box.top) * row_stride;
119    row_size = (box.right - box.left) * pixel_stride;
120    data = pixels + box.top * row_stride + box.left * pixel_stride;
121    return true;
122  }
123
124  void WriteCroppedPixels() const {
125    int out_size = row_size * box.height();
126    unsigned char* out = new unsigned char[out_size];
127    unsigned char* dst = out;
128    for (const unsigned char* row = data;
129        row < data + total_size;
130        row += row_stride, dst += row_size) {
131      // No change in pixel_stride, so we can copy whole rows.
132      memcpy(dst, row, row_size);
133    }
134
135    WriteResponse(out, out_size);
136    delete[] out;
137  }
138
139  unsigned char* pixels;
140  Box box;
141  // Points at the top-left pixel in |pixels|.
142  const unsigned char* data;
143  // These counts are in bytes.
144  int row_stride;
145  int pixel_stride;
146  int total_size;
147  int row_size;
148};
149
150
151static inline
152bool PixelsEqual(const unsigned char* pixel1, const unsigned char* pixel2,
153                 int tolerance) {
154  // Note: this works for both RGB and RGBA. Alpha channel is ignored.
155  return (abs(pixel1[0] - pixel2[0]) <= tolerance) &&
156         (abs(pixel1[1] - pixel2[1]) <= tolerance) &&
157         (abs(pixel1[2] - pixel2[2]) <= tolerance);
158}
159
160
161static inline
162bool PixelsEqual(const unsigned char* pixel, int color, int tolerance) {
163  unsigned char pixel2[3] = { color >> 16, color >> 8, color };
164  return PixelsEqual(pixel, pixel2, tolerance);
165}
166
167
168static
169bool Histogram(const Bitmap& bmp) {
170  int ignore_color;
171  int tolerance;
172  if (!(ReadInt(&ignore_color) && ReadInt(&tolerance))) {
173    fprintf(stderr, "Could not parse HISTOGRAM command.\n");
174    return false;
175  }
176
177  const int kLength = 3 * 256;
178  int counts[kLength] = {};
179
180  for (const unsigned char* row = bmp.data; row < bmp.data + bmp.total_size;
181       row += bmp.row_stride) {
182    for (const unsigned char* pixel = row; pixel < row + bmp.row_size;
183       pixel += bmp.pixel_stride) {
184      if (ignore_color >= 0 && PixelsEqual(pixel, ignore_color, tolerance))
185        continue;
186      ++(counts[256 * 0 + pixel[0]]);
187      ++(counts[256 * 1 + pixel[1]]);
188      ++(counts[256 * 2 + pixel[2]]);
189    }
190  }
191
192  WriteResponse(counts, sizeof(counts));
193  return true;
194}
195
196
197static
198bool BoundingBox(const Bitmap& bmp) {
199  int color;
200  int tolerance;
201  if (!(ReadInt(&color) && ReadInt(&tolerance))) {
202    fprintf(stderr, "Could not parse BOUNDING_BOX command.\n");
203    return false;
204  }
205
206  Box box;
207  box.left = bmp.total_size;
208  box.top = bmp.total_size;
209  box.right = 0;
210  box.bottom = 0;
211
212  int count = 0;
213  int y = 0;
214  for (const unsigned char* row = bmp.data; row < bmp.data + bmp.total_size;
215       row += bmp.row_stride, ++y) {
216    int x = 0;
217    for (const unsigned char* pixel = row; pixel < row + bmp.row_size;
218         pixel += bmp.pixel_stride, ++x) {
219      if (!PixelsEqual(pixel, color, tolerance))
220        continue;
221      box.Union(x, y);
222      ++count;
223    }
224  }
225
226  int response[] = { box.left, box.top, box.width(), box.height(), count };
227  WriteResponse(response, sizeof(response));
228  return true;
229}
230
231
232int main() {
233  Bitmap bmp;
234  int command;
235
236#if defined(WIN32)
237  _setmode(_fileno(stdin), _O_BINARY);
238  _setmode(_fileno(stdout), _O_BINARY);
239#else
240  FILE* unused_stdin = freopen(NULL, "rb", stdin);
241  FILE* unused_stdout = freopen(NULL, "wb", stdout);
242#endif
243
244  if (!bmp.Read()) return -1;
245  if (!ReadInt(&command)) {
246    fprintf(stderr, "Expected command.\n");
247    return -1;
248  }
249  switch (command) {
250    case CROP_PIXELS:
251      bmp.WriteCroppedPixels();
252      break;
253    case BOUNDING_BOX:
254      if (!BoundingBox(bmp)) return -1;
255      break;
256    case HISTOGRAM:
257      if (!Histogram(bmp)) return -1;
258      break;
259    default:
260      fprintf(stderr, "Unrecognized command\n");
261      return -1;
262  }
263  return 0;
264}
265