1// Copyright (c) 2013 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 <sstream>
6
7#include "ppapi/c/pp_errors.h"
8#include "ppapi/cpp/completion_callback.h"
9#include "ppapi/cpp/graphics_2d.h"
10#include "ppapi/cpp/image_data.h"
11#include "ppapi/cpp/input_event.h"
12#include "ppapi/cpp/instance.h"
13#include "ppapi/cpp/module.h"
14#include "ppapi/cpp/rect.h"
15#include "ppapi/cpp/var.h"
16#include "ppapi/utility/completion_callback_factory.h"
17
18// When compiling natively on Windows, PostMessage can be #define-d to
19// something else.
20#ifdef PostMessage
21#undef PostMessage
22#endif
23
24// Example plugin to demonstrate usage of pp::View and pp::Graphics2D APIs for
25// rendering 2D graphics at device resolution. See Paint() for more details.
26class MyInstance : public pp::Instance {
27 public:
28  explicit MyInstance(PP_Instance instance)
29      : pp::Instance(instance),
30        width_(0),
31        height_(0),
32        pixel_width_(0),
33        pixel_height_(0),
34        device_scale_(1.0f),
35        css_scale_(1.0f),
36        using_device_pixels_(true) {
37    RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE |
38                       PP_INPUTEVENT_CLASS_KEYBOARD);
39  }
40
41  virtual void DidChangeView(const pp::View& view) {
42    pp::Rect view_rect = view.GetRect();
43    if (view_rect.width() == width_ &&
44        view_rect.height() == height_ &&
45        view.GetDeviceScale() == device_scale_ &&
46        view.GetCSSScale() == css_scale_)
47      return;  // We don't care about the position, only the size and scale.
48
49    width_ = view_rect.width();
50    height_ = view_rect.height();
51    device_scale_ = view.GetDeviceScale();
52    css_scale_ = view.GetCSSScale();
53
54    pixel_width_ = width_ * device_scale_;
55    pixel_height_ = height_ * device_scale_;
56
57    SetupGraphics();
58  }
59
60  virtual bool HandleInputEvent(const pp::InputEvent& event) {
61    switch (event.GetType()) {
62      case PP_INPUTEVENT_TYPE_MOUSEDOWN:
63        HandleMouseDown(event);
64        return true;
65      default:
66        return false;
67    }
68  }
69
70  virtual void HandleMessage(const pp::Var& message_data) {
71    if (message_data.is_string()) {
72      std::string str = message_data.AsString();
73      if (str == "dip") {
74        if (using_device_pixels_) {
75          using_device_pixels_ = false;
76          SetupGraphics();
77        }
78      } else if (str == "device") {
79        if (!using_device_pixels_) {
80          using_device_pixels_ = true;
81          SetupGraphics();
82        }
83      } else if (str == "metrics") {
84        std::stringstream stream;
85        stream << "DIP (" << width_ << ", " << height_ << "), device pixels=("
86               << pixel_width_ << ", " << pixel_height_ <<"), device_scale="
87               << device_scale_ <<", css_scale=" << css_scale_;
88        PostMessage(stream.str());
89      }
90    }
91  }
92
93 private:
94  void HandleMouseDown(const pp::InputEvent& event) {
95    pp::MouseInputEvent mouse_event(event);
96    pp::Point position(mouse_event.GetPosition());
97    pp::Point position_device(position.x() * device_scale_,
98                              position.y() * device_scale_);
99    std::stringstream stream;
100    stream << "Mousedown at DIP (" << position.x() << ", " << position.y()
101           << "), device pixel (" << position_device.x() << ", "
102           << position_device.y() << ")";
103    if (css_scale_ > 0.0f) {
104      pp::Point position_css(position.x() / css_scale_,
105                             position.y() / css_scale_);
106      stream << ", CSS pixel (" << position_css.x() << ", " << position_css.y()
107             <<")";
108    } else {
109      stream <<", unknown CSS pixel. css_scale_=" << css_scale_;
110    }
111    PostMessage(stream.str());
112  }
113
114  void SetupGraphics() {
115    if (using_device_pixels_) {
116      // The plugin will treat 1 pixel in the device context as 1 device pixel.
117      // This will set up a properly-sized pp::Graphics2D, and tell Pepper
118      // to apply the correct scale so the resulting concatenated scale leaves
119      // each pixel in the device context as one on the display device.
120      device_context_ = pp::Graphics2D(this,
121                                       pp::Size(pixel_width_, pixel_height_),
122                                       true);
123      if (device_scale_ > 0.0f) {
124        // If SetScale is promoted to pp::Graphics2D, the dc_dev constructor
125        // can be removed, and this will become the following line instead.
126        // device_context_.SetScale(1.0f / device_scale_);
127        device_context_.SetScale(1.0f / device_scale_);
128      }
129    } else {
130      // The plugin will treat 1 pixel in the device context as one DIP.
131      device_context_ = pp::Graphics2D(this, pp::Size(width_, height_), true);
132    }
133    BindGraphics(device_context_);
134    Paint();
135  }
136
137  void Paint() {
138    int width = using_device_pixels_ ? pixel_width_ : width_;
139    int height = using_device_pixels_ ? pixel_height_ : height_;
140    pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
141                        pp::Size(width, height), false);
142    if (image.is_null())
143      return;
144
145    // Painting here will demonstrate a few techniques:
146    // - painting a thin blue box and cross-hatch to show the finest resolution
147    // available.
148    // - painting a 25 DIP (logical pixel) green circle to show how objects of a
149    //  fixed size in DIPs should be scaled if using a high-resolution
150    // pp::Graphics2D.
151    // - paiting a 50 CSS pixel red circle to show how objects of a fixed size
152    // in  CSS pixels should be scaled if using a high-resolution
153    // pp::Graphics2D, as well as how to use the GetCSSScale value properly.
154
155    // Painting in "DIP resolution" mode (|using_device_pixels_| false) will
156    // demonstrate how unscaled graphics would look, even on a high-DPI device.
157    // Painting in "device resolution" mode (|using_device_pixels_| true) will
158    // show how scaled graphics would look crisper on a high-DPI device, in
159    // comparison to using unscaled graphics. Both modes should look identical
160    // when displayed on a non-high-DPI device (window.devicePixelRatio == 1).
161    // Toggling between "DIP resolution" mode and "device resolution" mode
162    // should not change the sizes of the circles.
163
164    // Changing the browser zoom level should cause the CSS circle to zoom, but
165    // not the DIP-sized circle.
166
167    // All painting here does not use any anti-aliasing.
168    float circle_1_radius = 25;
169    if (using_device_pixels_)
170      circle_1_radius *= device_scale_;
171
172    float circle_2_radius = 50 * css_scale_;
173    if (using_device_pixels_)
174      circle_2_radius *= device_scale_;
175
176    for (int y = 0; y < height; ++y) {
177      char* row = static_cast<char*>(image.data()) + (y * image.stride());
178      uint32_t* pixel = reinterpret_cast<uint32_t*>(row);
179      for (int x = 0; x < width; ++x) {
180        int dx = (width / 2) - x;
181        int dy = (height / 2) - y;
182        float dist_squared = (dx * dx) + (dy * dy);
183        if (x == 0 || y == 0 || x == width - 1 || y == width - 1 || x == y ||
184            width - x - 1 == y) {
185          *pixel++ = 0xFF0000FF;
186        } else if (dist_squared < circle_1_radius * circle_1_radius) {
187          *pixel++ = 0xFF00FF00;
188        } else if (dist_squared < circle_2_radius * circle_2_radius) {
189          *pixel++ = 0xFFFF0000;
190        } else {
191          *pixel++ = 0xFF000000;
192        }
193      }
194    }
195
196    device_context_.ReplaceContents(&image);
197    device_context_.Flush(pp::CompletionCallback(&OnFlush, this));
198  }
199
200  static void OnFlush(void* user_data, int32_t result) {}
201
202  pp::Graphics2D device_context_;
203  int width_;
204  int height_;
205  int pixel_width_;
206  int pixel_height_;
207  float device_scale_;
208  float css_scale_;
209  bool using_device_pixels_;
210};
211
212// This object is the global object representing this plugin library as long as
213// it is loaded.
214class MyModule : public pp::Module {
215 public:
216  MyModule() : pp::Module() {}
217  virtual ~MyModule() {}
218
219  // Override CreateInstance to create your customized Instance object.
220  virtual pp::Instance* CreateInstance(PP_Instance instance) {
221    return new MyInstance(instance);
222  }
223};
224
225namespace pp {
226
227// Factory function for your specialization of the Module object.
228Module* CreateModule() {
229  return new MyModule();
230}
231
232}  // namespace pp
233