1// Copyright (c) 2012 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 <algorithm>
6#include <deque>
7#include <string>
8
9#include "ppapi/cpp/graphics_2d.h"
10#include "ppapi/cpp/image_data.h"
11#include "ppapi/cpp/instance.h"
12#include "ppapi/cpp/module.h"
13#include "ppapi/cpp/var.h"
14#include "ppapi/cpp/var_array_buffer.h"
15#include "ppapi/utility/completion_callback_factory.h"
16
17#ifdef WIN32
18#undef min
19#undef max
20#undef PostMessage
21
22// Allow 'this' in initializer list
23#pragma warning(disable : 4355)
24// Disable warning about behaviour of array initialization.
25#pragma warning(disable : 4351)
26#endif
27
28namespace {
29
30const uint32_t kBlue = 0xff4040ffu;
31const uint32_t kBlack = 0xff000000u;
32const size_t kHistogramSize = 256u;
33
34}  // namespace
35
36class VarArrayBufferInstance : public pp::Instance {
37 public:
38  explicit VarArrayBufferInstance(PP_Instance instance)
39      : pp::Instance(instance),
40        callback_factory_(this),
41        flushing_(false),
42        histogram_() {}
43  virtual ~VarArrayBufferInstance() {}
44
45 private:
46  /// Handler for messages coming in from the browser via postMessage().  The
47  /// @a var_message can contain anything: a JSON string; a string that encodes
48  /// method names and arguments; etc.
49  ///
50  /// In this case, we only handle <code>pp::VarArrayBuffer</code>s. When we
51  /// receive one, we compute and display a histogram based on its contents.
52  ///
53  /// @param[in] var_message The message posted by the browser.
54  virtual void HandleMessage(const pp::Var& var_message) {
55    if (var_message.is_array_buffer()) {
56      pp::VarArrayBuffer buffer(var_message);
57      ComputeHistogram(buffer);
58      DrawHistogram();
59    }
60  }
61
62  /// Create and return a blank (all-black) <code>pp::ImageData</code> of the
63  /// given <code>size</code>.
64  pp::ImageData MakeBlankImageData(const pp::Size& size) {
65    const bool init_to_zero = false;
66    pp::ImageData image_data =
67        pp::ImageData(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, init_to_zero);
68    uint32_t* image_buffer = static_cast<uint32_t*>(image_data.data());
69    for (int i = 0; i < size.GetArea(); ++i)
70      image_buffer[i] = kBlack;
71    return image_data;
72  }
73
74  /// Draw a bar of the appropriate height based on <code>value</code> at
75  /// <code>column</code> in <code>image_data</code>. <code>value</code> must be
76  /// in the range [0, 1].
77  void DrawBar(uint32_t column, double value, pp::ImageData* image_data) {
78    assert((value >= 0.0) && (value <= 1.0));
79    uint32_t* image_buffer = static_cast<uint32_t*>(image_data->data());
80    const uint32_t image_height = image_data->size().height();
81    const uint32_t image_width = image_data->size().width();
82    assert(column < image_width);
83    int bar_height = static_cast<int>(value * image_height);
84    for (int i = 0; i < bar_height; ++i) {
85      uint32_t row = image_height - 1 - i;
86      image_buffer[row * image_width + column] = kBlue;
87    }
88  }
89
90  void PaintAndFlush(pp::ImageData* image_data) {
91    assert(!flushing_);
92    graphics_2d_context_.ReplaceContents(image_data);
93    graphics_2d_context_.Flush(
94        callback_factory_.NewCallback(&VarArrayBufferInstance::DidFlush));
95    flushing_ = true;
96  }
97
98  /// The callback that gets invoked when a flush completes. This is bound to a
99  /// <code>CompletionCallback</code> and passed as a parameter to
100  /// <code>Flush</code>.
101  void DidFlush(int32_t error_code) {
102    flushing_ = false;
103    // If there are no images in the queue, we're done for now.
104    if (paint_queue_.empty())
105      return;
106    // Otherwise, pop the next image off the queue and draw it.
107    pp::ImageData image_data = paint_queue_.front();
108    paint_queue_.pop_front();
109    PaintAndFlush(&image_data);
110  }
111
112  virtual void DidChangeView(const pp::View& view) {
113    if (size_ != view.GetRect().size()) {
114      size_ = view.GetRect().size();
115      const bool is_always_opaque = true;
116      graphics_2d_context_ =
117          pp::Graphics2D(this, view.GetRect().size(), is_always_opaque);
118      BindGraphics(graphics_2d_context_);
119      // The images in our queue are the wrong size, so we won't paint them.
120      // We'll only draw the most recently computed histogram.
121      paint_queue_.clear();
122      DrawHistogram();
123    }
124  }
125
126  /// Compute and normalize a histogram based on the given VarArrayBuffer.
127  void ComputeHistogram(pp::VarArrayBuffer& buffer) {
128    std::fill_n(histogram_, kHistogramSize, 0.0);
129    uint32_t buffer_size = buffer.ByteLength();
130    if (buffer_size == 0)
131      return;
132    uint8_t* buffer_data = static_cast<uint8_t*>(buffer.Map());
133    for (uint32_t i = 0; i < buffer_size; ++i)
134      histogram_[buffer_data[i]] += 1.0;
135    // Normalize.
136    double max = *std::max_element(histogram_, histogram_ + kHistogramSize);
137    for (uint32_t i = 0; i < kHistogramSize; ++i)
138      histogram_[i] /= max;
139  }
140
141  /// Draw the current histogram_ in to an pp::ImageData, then paint and flush
142  /// that image. If we're already waiting on a flush, push it on to
143  /// <code>paint_queue_</code> to paint later.
144  void DrawHistogram() {
145    pp::ImageData image_data = MakeBlankImageData(size_);
146    for (int i = 0; i < std::min(static_cast<int>(kHistogramSize),
147                                 image_data.size().width());
148         ++i) {
149      DrawBar(i, histogram_[i], &image_data);
150    }
151
152    if (!flushing_)
153      PaintAndFlush(&image_data);
154    else
155      paint_queue_.push_back(image_data);
156  }
157
158  pp::Graphics2D graphics_2d_context_;
159  pp::CompletionCallbackFactory<VarArrayBufferInstance> callback_factory_;
160
161  /// A queue of images to paint. We must maintain a queue because we can not
162  /// call pp::Graphics2D::Flush while a Flush is already pending.
163  std::deque<pp::ImageData> paint_queue_;
164
165  /// The size of our rectangle in the DOM, as of the last time DidChangeView
166  /// was called.
167  pp::Size size_;
168
169  /// true iff we are flushing.
170  bool flushing_;
171
172  /// Stores the most recent histogram so that we can re-draw it if we get
173  /// resized.
174  double histogram_[kHistogramSize];
175};
176
177class VarArrayBufferModule : public pp::Module {
178 public:
179  VarArrayBufferModule() : pp::Module() {}
180  virtual ~VarArrayBufferModule() {}
181
182  virtual pp::Instance* CreateInstance(PP_Instance instance) {
183    return new VarArrayBufferInstance(instance);
184  }
185};
186
187namespace pp {
188Module* CreateModule() { return new VarArrayBufferModule(); }
189}  // namespace pp
190