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.
8 #include "ppapi/c/ppb_image_data.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/point.h"
15 #include "ppapi/utility/completion_callback_factory.h"
19 // Allow 'this' in initializer list
20 #pragma warning(disable : 4355)
25 static const int kMouseRadius
= 20;
27 uint8_t RandUint8(uint8_t min
, uint8_t max
) {
29 uint8_t result
= static_cast<uint8_t>(r
* (max
- min
+ 1) / RAND_MAX
) + min
;
33 uint32_t MakeColor(uint8_t r
, uint8_t g
, uint8_t b
) {
35 PP_ImageDataFormat format
= pp::ImageData::GetNativeImageDataFormat();
36 if (format
== PP_IMAGEDATAFORMAT_BGRA_PREMUL
) {
37 return (a
<< 24) | (r
<< 16) | (g
<< 8) | b
;
39 return (a
<< 24) | (b
<< 16) | (g
<< 8) | r
;
45 class Graphics2DInstance
: public pp::Instance
{
47 explicit Graphics2DInstance(PP_Instance instance
)
48 : pp::Instance(instance
),
49 callback_factory_(this),
52 device_scale_(1.0f
) {}
54 ~Graphics2DInstance() { delete[] buffer_
; }
56 virtual bool Init(uint32_t argc
, const char* argn
[], const char* argv
[]) {
57 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE
);
59 unsigned int seed
= 1;
65 virtual void DidChangeView(const pp::View
& view
) {
66 device_scale_
= view
.GetDeviceScale();
67 pp::Size new_size
= pp::Size(view
.GetRect().width() * device_scale_
,
68 view
.GetRect().height() * device_scale_
);
70 if (!CreateContext(new_size
))
73 // When flush_context_ is null, it means there is no Flush callback in
74 // flight. This may have happened if the context was not created
75 // successfully, or if this is the first call to DidChangeView (when the
76 // module first starts). In either case, start the main loop.
77 if (flush_context_
.is_null())
81 virtual bool HandleInputEvent(const pp::InputEvent
& event
) {
85 if (event
.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN
||
86 event
.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE
) {
87 pp::MouseInputEvent
mouse_event(event
);
89 if (mouse_event
.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_NONE
)
92 mouse_
= pp::Point(mouse_event
.GetPosition().x() * device_scale_
,
93 mouse_event
.GetPosition().y() * device_scale_
);
97 if (event
.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP
)
104 void CreatePalette() {
105 for (int i
= 0; i
< 64; ++i
) {
107 palette_
[i
] = MakeColor(i
* 2, 0, 0);
108 palette_
[i
+ 64] = MakeColor(128 + i
* 2, 0, 0);
110 palette_
[i
+ 128] = MakeColor(255, i
* 4, 0);
112 palette_
[i
+ 192] = MakeColor(255, 255, i
* 4);
116 bool CreateContext(const pp::Size
& new_size
) {
117 const bool kIsAlwaysOpaque
= true;
118 context_
= pp::Graphics2D(this, new_size
, kIsAlwaysOpaque
);
119 // Call SetScale before BindGraphics so the image is scaled correctly on
121 context_
.SetScale(1.0f
/ device_scale_
);
122 if (!BindGraphics(context_
)) {
123 fprintf(stderr
, "Unable to bind 2d context!\n");
124 context_
= pp::Graphics2D();
128 // Allocate a buffer of palette entries of the same size as the new context.
129 buffer_
= new uint8_t[new_size
.width() * new_size
.height()];
136 // Old-school fire technique cribbed from
137 // http://ionicsolutions.net/2011/12/30/demo-fire-effect/
144 int width
= size_
.width();
145 int height
= size_
.height();
148 // Draw two rows of random values at the bottom.
149 for (int y
= height
- 2; y
< height
; ++y
) {
150 size_t offset
= y
* width
;
151 for (int x
= 0; x
< width
; ++x
) {
152 // On a random chance, draw some longer strips of brighter colors.
153 if (span
|| RandUint8(1, 4) == 1) {
155 span
= RandUint8(10, 20);
156 buffer_
[offset
+ x
] = RandUint8(128, 255);
159 buffer_
[offset
+ x
] = RandUint8(32, 96);
165 void UpdateFlames() {
166 int width
= size_
.width();
167 int height
= size_
.height();
168 for (int y
= 1; y
< height
- 1; ++y
) {
169 size_t offset
= y
* width
;
170 for (int x
= 1; x
< width
- 1; ++x
) {
172 sum
+= buffer_
[offset
- width
+ x
- 1];
173 sum
+= buffer_
[offset
- width
+ x
+ 1];
174 sum
+= buffer_
[offset
+ x
- 1];
175 sum
+= buffer_
[offset
+ x
+ 1];
176 sum
+= buffer_
[offset
+ width
+ x
- 1];
177 sum
+= buffer_
[offset
+ width
+ x
];
178 sum
+= buffer_
[offset
+ width
+ x
+ 1];
179 buffer_
[offset
- width
+ x
] = sum
/ 7;
188 int width
= size_
.width();
189 int height
= size_
.height();
191 // Draw a circle at the mouse position.
192 int radius
= kMouseRadius
* device_scale_
;
195 int minx
= cx
- radius
<= 0 ? 1 : cx
- radius
;
196 int maxx
= cx
+ radius
>= width
? width
- 1 : cx
+ radius
;
197 int miny
= cy
- radius
<= 0 ? 1 : cy
- radius
;
198 int maxy
= cy
+ radius
>= height
? height
- 1 : cy
+ radius
;
199 for (int y
= miny
; y
< maxy
; ++y
) {
200 for (int x
= minx
; x
< maxx
; ++x
) {
201 if ((x
- cx
) * (x
- cx
) + (y
- cy
) * (y
- cy
) < radius
* radius
)
202 buffer_
[y
* width
+ x
] = RandUint8(192, 255);
208 // See the comment above the call to ReplaceContents below.
209 PP_ImageDataFormat format
= pp::ImageData::GetNativeImageDataFormat();
210 const bool kDontInitToZero
= false;
211 pp::ImageData
image_data(this, format
, size_
, kDontInitToZero
);
213 uint32_t* data
= static_cast<uint32_t*>(image_data
.data());
217 uint32_t num_pixels
= size_
.width() * size_
.height();
219 for (uint32_t i
= 0; i
< num_pixels
; ++i
) {
220 data
[offset
] = palette_
[buffer_
[offset
]];
224 // Using Graphics2D::ReplaceContents is the fastest way to update the
225 // entire canvas every frame. According to the documentation:
227 // Normally, calling PaintImageData() requires that the browser copy
228 // the pixels out of the image and into the graphics context's backing
229 // store. This function replaces the graphics context's backing store
230 // with the given image, avoiding the copy.
232 // In the case of an animation, you will want to allocate a new image for
233 // the next frame. It is best if you wait until the flush callback has
234 // executed before allocating this bitmap. This gives the browser the
235 // option of caching the previous backing store and handing it back to
236 // you (assuming the sizes match). In the optimal case, this means no
237 // bitmaps are allocated during the animation, and the backing store and
238 // "front buffer" (which the module is painting into) are just being
239 // swapped back and forth.
241 context_
.ReplaceContents(&image_data
);
244 void MainLoop(int32_t) {
245 if (context_
.is_null()) {
246 // The current Graphics2D context is null, so updating and rendering is
247 // pointless. Set flush_context_ to null as well, so if we get another
248 // DidChangeView call, the main loop is started again.
249 flush_context_
= context_
;
255 // Store a reference to the context that is being flushed; this ensures
256 // the callback is called, even if context_ changes before the flush
258 flush_context_
= context_
;
260 callback_factory_
.NewCallback(&Graphics2DInstance::MainLoop
));
263 pp::CompletionCallbackFactory
<Graphics2DInstance
> callback_factory_
;
264 pp::Graphics2D context_
;
265 pp::Graphics2D flush_context_
;
270 uint32_t palette_
[256];
274 class Graphics2DModule
: public pp::Module
{
276 Graphics2DModule() : pp::Module() {}
277 virtual ~Graphics2DModule() {}
279 virtual pp::Instance
* CreateInstance(PP_Instance instance
) {
280 return new Graphics2DInstance(instance
);
285 Module
* CreateModule() { return new Graphics2DModule(); }