diff options
Diffstat (limited to 'ppapi/tests/test_graphics_2d.cc')
-rw-r--r-- | ppapi/tests/test_graphics_2d.cc | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/ppapi/tests/test_graphics_2d.cc b/ppapi/tests/test_graphics_2d.cc new file mode 100644 index 0000000..34b9945 --- /dev/null +++ b/ppapi/tests/test_graphics_2d.cc @@ -0,0 +1,542 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ppapi/tests/test_graphics_2d.h" + +#include <string.h> + +#include "ppapi/c/dev/ppb_testing_dev.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/ppb_graphics_2d.h" +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/graphics_2d.h" +#include "ppapi/cpp/image_data.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/tests/testing_instance.h" + +REGISTER_TEST_CASE(Graphics2D); + +namespace { + +// A NOP flush callback for use in various tests. +void FlushCallbackNOP(void* data, int32_t result) { +} + +void FlushCallbackQuitMessageLoop(void* data, int32_t result) { + reinterpret_cast<TestGraphics2D*>(data)->QuitMessageLoop(); +} + +} // namespace + +bool TestGraphics2D::Init() { + graphics_2d_interface_ = reinterpret_cast<PPB_Graphics2D const*>( + pp::Module::Get()->GetBrowserInterface(PPB_GRAPHICS_2D_INTERFACE)); + image_data_interface_ = reinterpret_cast<PPB_ImageData const*>( + pp::Module::Get()->GetBrowserInterface(PPB_IMAGEDATA_INTERFACE)); + testing_interface_ = reinterpret_cast<PPB_Testing_Dev const*>( + pp::Module::Get()->GetBrowserInterface(PPB_TESTING_DEV_INTERFACE)); + if (!testing_interface_) { + // Give a more helpful error message for the testing interface being gone + // since that needs special enabling in Chrome. + instance_->AppendError("This test needs the testing interface, which is " + "not currently available. In Chrome, use --enable-pepper-testing when " + "launching."); + } + return graphics_2d_interface_ && image_data_interface_ && + testing_interface_; +} + +void TestGraphics2D::RunTest() { + instance_->LogTest("InvalidResource", TestInvalidResource()); + instance_->LogTest("InvalidSize", TestInvalidSize()); + instance_->LogTest("Humongous", TestHumongous()); + instance_->LogTest("InitToZero", TestInitToZero()); + instance_->LogTest("Describe", TestDescribe()); + instance_->LogTest("Paint", TestPaint()); + //instance_->LogTest("Scroll", TestScroll()); // TODO(brettw) implement. + instance_->LogTest("Replace", TestReplace()); + instance_->LogTest("Flush", TestFlush()); +} + +void TestGraphics2D::QuitMessageLoop() { + testing_interface_->QuitMessageLoop(); +} + +bool TestGraphics2D::ReadImageData(const pp::Graphics2D& dc, + pp::ImageData* image, + const pp::Point& top_left) const { + return testing_interface_->ReadImageData(dc.pp_resource(), + image->pp_resource(), + &top_left.pp_point()); +} + +bool TestGraphics2D::IsDCUniformColor(const pp::Graphics2D& dc, + uint32_t color) const { + pp::ImageData readback(PP_IMAGEDATAFORMAT_BGRA_PREMUL, + dc.size(), false); + if (readback.is_null()) + return false; + if (!ReadImageData(dc, &readback, pp::Point(0, 0))) + return false; + return IsSquareInImage(readback, 0, pp::Rect(dc.size()), color); +} + +bool TestGraphics2D::FlushAndWaitForDone(pp::Graphics2D* context) { + pp::CompletionCallback cc(&FlushCallbackQuitMessageLoop, this); + int32_t rv = context->Flush(cc); + if (rv == PP_OK) + return true; + if (rv != PP_ERROR_WOULDBLOCK) + return false; + testing_interface_->RunMessageLoop(); + return true; +} + +void TestGraphics2D::FillRectInImage(pp::ImageData* image, + const pp::Rect& rect, + uint32_t color) const { + for (int y = rect.y(); y < rect.bottom(); y++) { + uint32_t* row = image->GetAddr32(pp::Point(rect.x(), y)); + for (int pixel = 0; pixel < rect.width(); pixel++) + row[pixel] = color; + } +} + +void TestGraphics2D::FillImageWithGradient(pp::ImageData* image) const { + for (int y = 0; y < image->size().height(); y++) { + uint32_t red = ((y * 256) / image->size().height()) & 0xFF; + for (int x = 0; x < image->size().width(); x++) { + uint32_t green = ((x * 256) / image->size().width()) & 0xFF; + uint32_t blue = ((red + green) / 2) & 0xFF; + uint32_t* pixel = image->GetAddr32(pp::Point(x, y)); + *pixel = (blue << 24) | (green << 16) | (red << 8); + } + } +} + +bool TestGraphics2D::CompareImages(const pp::ImageData& image1, + const pp::ImageData& image2) { + return CompareImageRect( + image1, pp::Rect(0, 0, image1.size().width(), image1.size().height()), + image2, pp::Rect(0, 0, image2.size().width(), image2.size().height())); +} + +bool TestGraphics2D::CompareImageRect(const pp::ImageData& image1, + const pp::Rect& rc1, + const pp::ImageData& image2, + const pp::Rect& rc2) const { + if (rc1.width() != rc2.width() || rc1.height() != rc2.height()) + return false; + + for (int y = 0; y < rc1.height(); y++) { + for (int x = 0; x < rc1.width(); x++) { + if (*(image1.GetAddr32(pp::Point(rc1.x() + x, rc1.y() + y))) != + *(image2.GetAddr32(pp::Point(rc2.x() + x, rc2.y() + y)))) + return false; + } + } + return true; +} + +bool TestGraphics2D::IsSquareInImage(const pp::ImageData& image_data, + uint32_t background_color, + const pp::Rect& square, + uint32_t square_color) const { + for (int y = 0; y < image_data.size().height(); y++) { + for (int x = 0; x < image_data.size().width(); x++) { + uint32_t pixel = *image_data.GetAddr32(pp::Point(x, y)); + uint32_t desired_color; + if (square.Contains(x, y)) + desired_color = square_color; + else + desired_color = background_color; + if (pixel != desired_color) + return false; + } + } + return true; +} + +bool TestGraphics2D::IsSquareInDC(const pp::Graphics2D& dc, + uint32_t background_color, + const pp::Rect& square, + uint32_t square_color) const { + pp::ImageData readback(PP_IMAGEDATAFORMAT_BGRA_PREMUL, + dc.size(), false); + if (readback.is_null()) + return false; + if (!ReadImageData(dc, &readback, pp::Point(0, 0))) + return false; + return IsSquareInImage(readback, background_color, square, square_color); +} + +// Test all the functions with an invalid handle. Most of these just check for +// a crash since the browser don't return a value. +std::string TestGraphics2D::TestInvalidResource() { + pp::Graphics2D null_context; + pp::ImageData image(PP_IMAGEDATAFORMAT_BGRA_PREMUL, pp::Size(16, 16), true); + + // Describe. + PP_Size size; + bool opaque; + graphics_2d_interface_->Describe(image.pp_resource(), &size, &opaque); + graphics_2d_interface_->Describe(null_context.pp_resource(), + &size, &opaque); + + // PaintImageData. + PP_Point zero_zero; + zero_zero.x = 0; + zero_zero.y = 0; + graphics_2d_interface_->PaintImageData(image.pp_resource(), + image.pp_resource(), + &zero_zero, NULL); + graphics_2d_interface_->PaintImageData(null_context.pp_resource(), + image.pp_resource(), + &zero_zero, NULL); + + // Scroll. + PP_Point zero_ten; + zero_ten.x = 0; + zero_ten.y = 10; + graphics_2d_interface_->Scroll(image.pp_resource(), NULL, &zero_ten); + graphics_2d_interface_->Scroll(null_context.pp_resource(), + NULL, &zero_ten); + + // ReplaceContents. + graphics_2d_interface_->ReplaceContents(image.pp_resource(), + image.pp_resource()); + graphics_2d_interface_->ReplaceContents(null_context.pp_resource(), + image.pp_resource()); + + // Flush. + if (graphics_2d_interface_->Flush( + image.pp_resource(), + PP_MakeCompletionCallback(&FlushCallbackNOP, NULL)) == PP_OK) + return "Flush succeeded with a different resource"; + if (graphics_2d_interface_->Flush( + null_context.pp_resource(), + PP_MakeCompletionCallback(&FlushCallbackNOP, NULL)) == PP_OK) + return "Flush succeeded with a NULL resource"; + + // ReadImageData. + if (testing_interface_->ReadImageData(image.pp_resource(), + image.pp_resource(), + &zero_zero)) + return "ReadImageData succeeded with a different resource"; + if (testing_interface_->ReadImageData(null_context.pp_resource(), + image.pp_resource(), + &zero_zero)) + return "ReadImageData succeeded with a NULL resource"; + + return ""; +} + +std::string TestGraphics2D::TestInvalidSize() { + pp::Graphics2D a(pp::Size(16, 0), false); + if (!a.is_null()) + return "0 height accepted"; + + pp::Graphics2D b(pp::Size(0, 16), false); + if (!b.is_null()) + return "0 width accepted"; + + // Need to use the C API since pp::Size prevents negative sizes. + PP_Size size; + size.width = 16; + size.height = -16; + ASSERT_FALSE(!!graphics_2d_interface_->Create( + pp::Module::Get()->pp_module(), &size, false)); + + size.width = -16; + size.height = 16; + ASSERT_FALSE(!!graphics_2d_interface_->Create( + pp::Module::Get()->pp_module(), &size, false)); + + return ""; +} + +std::string TestGraphics2D::TestHumongous() { + pp::Graphics2D a(pp::Size(100000, 100000), false); + if (!a.is_null()) + return "Humongous device created"; + return ""; +} + +std::string TestGraphics2D::TestInitToZero() { + const int w = 15, h = 17; + pp::Graphics2D dc(pp::Size(w, h), false); + if (dc.is_null()) + return "Failure creating a boring device"; + + // Make an image with nonzero data in it (so we can test that zeros were + // actually read versus ReadImageData being a NOP). + pp::ImageData image(PP_IMAGEDATAFORMAT_BGRA_PREMUL, pp::Size(w, h), true); + if (image.is_null()) + return "Failure to allocate an image"; + memset(image.data(), 0xFF, image.stride() * image.size().height() * 4); + + // Read out the initial data from the device & check. + if (!ReadImageData(dc, &image, pp::Point(0, 0))) + return "Couldn't read image data"; + if (!IsSquareInImage(image, 0, pp::Rect(0, 0, w, h), 0)) + return "Got a nonzero pixel"; + + return ""; +} + +std::string TestGraphics2D::TestDescribe() { + const int w = 15, h = 17; + pp::Graphics2D dc(pp::Size(w, h), false); + if (dc.is_null()) + return "Failure creating a boring device"; + + PP_Size size; + size.width = -1; + size.height = -1; + bool is_always_opaque = true; + if (!graphics_2d_interface_->Describe(dc.pp_resource(), &size, + &is_always_opaque)) + return "Describe failed"; + if (size.width != w || size.height != h || is_always_opaque != false) + return "Mismatch of data."; + + return ""; +} + +std::string TestGraphics2D::TestPaint() { + const int w = 15, h = 17; + pp::Graphics2D dc(pp::Size(w, h), false); + if (dc.is_null()) + return "Failure creating a boring device"; + + // Make sure the device background is 0. + if (!IsDCUniformColor(dc, 0)) + return "Bad initial color"; + + // Fill the backing store with white. + const uint32_t background_color = 0xFFFFFFFF; + pp::ImageData background(PP_IMAGEDATAFORMAT_BGRA_PREMUL, pp::Size(w, h), + false); + FillRectInImage(&background, pp::Rect(0, 0, w, h), background_color); + dc.PaintImageData(background, pp::Point(0, 0)); + if (!FlushAndWaitForDone(&dc)) + return "Couldn't flush to fill backing store"; + + // Make an image to paint with that's opaque white and enqueue a paint. + const int fill_w = 2, fill_h = 3; + pp::ImageData fill(PP_IMAGEDATAFORMAT_BGRA_PREMUL, pp::Size(fill_w, fill_h), + true); + if (fill.is_null()) + return "Failure to allocate fill image"; + FillRectInImage(&fill, pp::Rect(fill.size()), background_color); + const int paint_x = 4, paint_y = 5; + dc.PaintImageData(fill, pp::Point(paint_x, paint_y)); + + // Validate that nothing has been actually painted. + if (!IsDCUniformColor(dc, background_color)) + return "Image updated before flush (or failure in readback)."; + + // The paint hasn't been flushed so we can still change the bitmap. Fill with + // 50% blue. This will also verify that the backing store is replaced + // with the contents rather than blended. + const uint32_t fill_color = 0x80000080; + FillRectInImage(&fill, pp::Rect(fill.size()), fill_color); + if (!FlushAndWaitForDone(&dc)) + return "Couldn't flush 50% blue paint"; + + if (!IsSquareInDC(dc, background_color, + pp::Rect(paint_x, paint_y, fill_w, fill_h), + fill_color)) + return "Image not painted properly."; + + // Reset the DC to blank white & paint our image slightly off the buffer. + // This should succeed. We also try painting the same thing where the + // dirty rect falls outeside of the device, which should fail. + dc.PaintImageData(background, pp::Point(0, 0)); + const int second_paint_x = -1, second_paint_y = -2; + dc.PaintImageData(fill, pp::Point(second_paint_x, second_paint_y)); + dc.PaintImageData(fill, pp::Point(second_paint_x, second_paint_y), + pp::Rect(-second_paint_x, -second_paint_y, 1, 1)); + if (!FlushAndWaitForDone(&dc)) + return "Couldn't flush second paint"; + + // Now we should have a little bit of the image peeking out the top left. + if (!IsSquareInDC(dc, background_color, pp::Rect(0, 0, 1, 1), + fill_color)) + return "Partially offscreen paint failed."; + + // Now repaint that top left pixel by doing a subset of the source image. + pp::ImageData subset(PP_IMAGEDATAFORMAT_BGRA_PREMUL, pp::Size(w, h), false); + uint32_t subset_color = 0x80808080; + const int subset_x = 2, subset_y = 1; + *subset.GetAddr32(pp::Point(subset_x, subset_y)) = subset_color; + dc.PaintImageData(subset, pp::Point(-subset_x, -subset_y), + pp::Rect(subset_x, subset_y, 1, 1)); + if (!FlushAndWaitForDone(&dc)) + return "Couldn't flush repaint"; + if (!IsSquareInDC(dc, background_color, pp::Rect(0, 0, 1, 1), + subset_color)) + return "Subset paint failed."; + + return ""; +} + +std::string TestGraphics2D::TestScroll() { + const int w = 115, h = 117; + pp::Graphics2D dc(pp::Size(w, h), false); + if (dc.is_null()) + return "Failure creating a boring device."; + + // Make sure the device background is 0. + if (!IsDCUniformColor(dc, 0)) + return "Bad initial color."; + + const int image_w = 15, image_h = 23; + pp::ImageData test_image(PP_IMAGEDATAFORMAT_BGRA_PREMUL, + pp::Size(image_w, image_h), false); + FillImageWithGradient(&test_image); + + int image_x = 51, image_y = 72; + dc.PaintImageData(test_image, pp::Point(image_x, image_y)); + if (!FlushAndWaitForDone(&dc)) + return "Couldn't flush to fill backing store."; + + // TC1, Scroll image to a free space. + int dx = -40, dy = -48; + pp::Rect clip = pp::Rect(image_x, image_y, test_image.size().width(), + test_image.size().height()); + dc.Scroll(clip, pp::Point(dx, dy)); + + if (!FlushAndWaitForDone(&dc)) + return "TC1, Couldn't flush to scroll."; + + image_x += dx; + image_y += dy; + + pp::ImageData readback(PP_IMAGEDATAFORMAT_BGRA_PREMUL, + pp::Size(image_w, image_h), false); + if (!ReadImageData(dc, &readback, pp::Point(image_x, image_y))) + return "TC1, Couldn't read back image data."; + + if (!CompareImages(test_image, readback)) + return "TC1, Read back image is not the same as test image."; + + // TC2, Scroll image to an overlapping space. + dx = 6; + dy = 9; + clip = pp::Rect(image_x, image_y, test_image.size().width(), + test_image.size().height()); + dc.Scroll(clip, pp::Point(dx, dy)); + + if (!FlushAndWaitForDone(&dc)) + return "TC2, Couldn't flush to scroll."; + + image_x += dx; + image_y += dy; + + if (!ReadImageData(dc, &readback, pp::Point(image_x, image_y))) + return "TC2, Couldn't read back image data."; + + if (!CompareImages(test_image, readback)) + return "TC2, Read back image is not the same as test image."; + + return ""; +} + +std::string TestGraphics2D::TestReplace() { + const int w = 15, h = 17; + pp::Graphics2D dc(pp::Size(w, h), false); + if (dc.is_null()) + return "Failure creating a boring device"; + + // Replacing with a different size image should fail. + pp::ImageData weird_size(PP_IMAGEDATAFORMAT_BGRA_PREMUL, + pp::Size(w - 1, h), true); + if (weird_size.is_null()) + return "Failure allocating the weird sized image"; + dc.ReplaceContents(&weird_size); + + // Fill the background with blue but don't flush yet. + const int32_t background_color = 0xFF0000FF; + pp::ImageData background(PP_IMAGEDATAFORMAT_BGRA_PREMUL, pp::Size(w, h), + true); + if (background.is_null()) + return "Failure to allocate background image"; + FillRectInImage(&background, pp::Rect(0, 0, w, h), background_color); + dc.PaintImageData(background, pp::Point(0, 0)); + + // Replace with a green background but don't flush yet. + const int32_t swapped_color = 0xFF0000FF; + pp::ImageData swapped(PP_IMAGEDATAFORMAT_BGRA_PREMUL, pp::Size(w, h), true); + if (swapped.is_null()) + return "Failure to allocate swapped image"; + FillRectInImage(&swapped, pp::Rect(0, 0, w, h), swapped_color); + dc.ReplaceContents(&swapped); + + // The background should be unchanged since we didn't flush yet. + if (!IsDCUniformColor(dc, 0)) + return "Image updated before flush (or failure in readback)."; + + // Test the C++ wrapper. The size of the swapped image should be reset. + if (swapped.pp_resource() || swapped.size().width() || + swapped.size().height() || swapped.data()) + return "Size of the swapped image should be reset."; + + // Painting with the swapped image should fail. + dc.PaintImageData(swapped, pp::Point(0, 0)); + + // Flush and make sure the result is correct. + if (!FlushAndWaitForDone(&dc)) + return "Couldn't flush"; + + // The background should be green from the swapped image. + if (!IsDCUniformColor(dc, swapped_color)) + return "Flushed color incorrect (or failure in readback)."; + + return ""; +} + +std::string TestGraphics2D::TestFlush() { + // Tests that synchronous flushes (NULL callback) fail on the main thread + // (which is the current one). + const int w = 15, h = 17; + pp::Graphics2D dc(pp::Size(w, h), false); + if (dc.is_null()) + return "Failure creating a boring device"; + + // Fill the background with blue but don't flush yet. + pp::ImageData background(PP_IMAGEDATAFORMAT_BGRA_PREMUL, pp::Size(w, h), + true); + if (background.is_null()) + return "Failure to allocate background image"; + dc.PaintImageData(background, pp::Point(0, 0)); + + int32_t rv = dc.Flush(pp::CompletionCallback::Block()); + if (rv == PP_OK || rv == PP_ERROR_WOULDBLOCK) + return "Flush succeeded from the main thread with no callback."; + + // Test flushing with no operations still issues a callback. + // (This may also hang if the browser never issues the callback). + pp::Graphics2D dc_nopaints(pp::Size(w, h), false); + if (dc.is_null()) + return "Failure creating the nopaint device"; + if (!FlushAndWaitForDone(&dc_nopaints)) + return "Couldn't flush the nopaint device"; + + // Test that multiple flushes fail if we don't get a callback in between. + rv = dc_nopaints.Flush(pp::CompletionCallback(&FlushCallbackNOP, NULL)); + if (rv != PP_OK && rv != PP_ERROR_WOULDBLOCK) + return "Couldn't flush first time for multiple flush test."; + + if (rv != PP_OK) { + // If the first flush would block, then a second should fail. + rv = dc_nopaints.Flush(pp::CompletionCallback(&FlushCallbackNOP, NULL)); + if (rv == PP_OK || rv == PP_ERROR_WOULDBLOCK) + return "Second flush succeeded before callback ran."; + } + + return ""; +} |