| // Copyright 2018 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/overlay_info/qr_code_overlay.h" |
| |
| #include <algorithm> |
| #include <string> |
| |
| #include "base/compiler_specific.h" |
| #include "base/logging.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cobalt/overlay_info/overlay_info_registry.h" |
| #include "cobalt/render_tree/animations/animate_node.h" |
| #include "starboard/memory.h" |
| #include "third_party/QR-Code-generator/cpp/QrCode.hpp" |
| |
| namespace cobalt { |
| namespace overlay_info { |
| |
| namespace { |
| |
| using qrcodegen::QrCode; |
| using render_tree::Image; |
| using render_tree::ImageNode; |
| using render_tree::ResourceProvider; |
| |
| const int kMinimumQrCodeVersion = 3; |
| const int kPixelSizeInBytes = 4; |
| #if SB_IS_BIG_ENDIAN |
| const uint32_t kBlack = 0x000000FF; |
| #else // SB_IS_BIG_ENDIAN |
| const uint32_t kBlack = 0xFF000000; |
| #endif // SB_IS_BIG_ENDIAN |
| const uint32_t kWhite = 0xFFFFFFFF; |
| const uint32_t kBorderColor = kWhite; |
| const int kScreenMarginInPixels = 64; |
| |
| uint32_t s_frame_index_ = 0; |
| |
| void DrawRect(int width, int height, int pitch_in_bytes, uint32_t color, |
| uint8_t* target_buffer) { |
| while (height > 0) { |
| uint32_t* pixels = reinterpret_cast<uint32_t*>(target_buffer); |
| for (int i = 0; i < width; ++i) { |
| pixels[i] = color; |
| } |
| target_buffer += pitch_in_bytes; |
| --height; |
| } |
| } |
| |
| void DrawQrCode(const QrCode& qr_code, int module_dimension_in_pixels, |
| int pitch_in_bytes, uint8_t* target_buffer) { |
| uint8_t* row_data = target_buffer; |
| for (int row = 0; row < qr_code.getSize(); ++row) { |
| uint8_t* column_data = row_data; |
| for (int column = 0; column < qr_code.getSize(); ++column) { |
| DrawRect(module_dimension_in_pixels, module_dimension_in_pixels, |
| pitch_in_bytes, qr_code.getModule(row, column) ? kBlack : kWhite, |
| column_data); |
| column_data += kPixelSizeInBytes * module_dimension_in_pixels; |
| } |
| |
| row_data += pitch_in_bytes * module_dimension_in_pixels; |
| } |
| } |
| |
| scoped_refptr<Image> CreateImageForQrCode(const QrCode& qr_code, |
| const math::Size& screen_size, |
| int slots, |
| ResourceProvider* resource_provider) { |
| TRACE_EVENT0("cobalt::overlay_info", "CreateImageForQrCode()"); |
| |
| const int module_dimension_in_pixels = screen_size.height() > 1080 ? 16 : 8; |
| const int code_border_in_pixels = module_dimension_in_pixels * 2; |
| |
| int qr_code_size_in_blocks = qr_code.getSize(); |
| |
| int column = (screen_size.width() - kScreenMarginInPixels * 2 - |
| code_border_in_pixels) / |
| (qr_code_size_in_blocks * module_dimension_in_pixels + |
| code_border_in_pixels); |
| column = std::min(column, slots); |
| int row = (slots + column - 1) / column; |
| |
| int image_width = |
| column * qr_code_size_in_blocks * module_dimension_in_pixels + |
| code_border_in_pixels * (column + 1); |
| int image_height = row * qr_code_size_in_blocks * module_dimension_in_pixels + |
| code_border_in_pixels * (row + 1); |
| |
| auto image_data = resource_provider->AllocateImageData( |
| math::Size(image_width, image_height), render_tree::kPixelFormatRGBA8, |
| render_tree::kAlphaFormatOpaque); |
| DCHECK(image_data); |
| auto image_desc = image_data->GetDescriptor(); |
| |
| uint32_t slot_index = 0; |
| auto row_data = image_data->GetMemory(); |
| for (int i = 0; i < row; ++i) { |
| // Draw the top border of all qr code blocks in the row. |
| DrawRect(image_width, code_border_in_pixels, image_desc.pitch_in_bytes, |
| kBorderColor, row_data); |
| row_data += code_border_in_pixels * image_desc.pitch_in_bytes; |
| auto column_data = row_data; |
| |
| for (int j = 0; j < column; ++j) { |
| // Draw the left border. |
| DrawRect(code_border_in_pixels, |
| qr_code_size_in_blocks * module_dimension_in_pixels, |
| image_desc.pitch_in_bytes, kBorderColor, column_data); |
| column_data += code_border_in_pixels * kPixelSizeInBytes; |
| if (slot_index == s_frame_index_ % slots) { |
| // Draw qr code. |
| DrawQrCode(qr_code, module_dimension_in_pixels, |
| image_desc.pitch_in_bytes, column_data); |
| } else { |
| DrawRect(qr_code_size_in_blocks * module_dimension_in_pixels, |
| qr_code_size_in_blocks * module_dimension_in_pixels, |
| image_desc.pitch_in_bytes, kBlack, column_data); |
| } |
| ++slot_index; |
| column_data += qr_code_size_in_blocks * module_dimension_in_pixels * |
| kPixelSizeInBytes; |
| } |
| |
| // Draw the right border of the row. |
| DrawRect(code_border_in_pixels, |
| qr_code_size_in_blocks * module_dimension_in_pixels, |
| image_desc.pitch_in_bytes, kBorderColor, column_data); |
| |
| row_data += qr_code_size_in_blocks * module_dimension_in_pixels * |
| image_desc.pitch_in_bytes; |
| } |
| |
| // Draw the bottom border of all qr code. |
| DrawRect(image_width, code_border_in_pixels, image_desc.pitch_in_bytes, |
| kBorderColor, row_data); |
| |
| return resource_provider->CreateImage(std::move(image_data)); |
| } |
| |
| void AnimateCB(math::Size screen_size, int slots, |
| ResourceProvider* resource_provider, |
| ImageNode::Builder* image_node, base::TimeDelta time) { |
| DCHECK(image_node); |
| |
| TRACE_EVENT0("cobalt::overlay_info", "AnimateCB()"); |
| |
| OverlayInfoRegistry::Register("frame", s_frame_index_); |
| ++s_frame_index_; |
| |
| std::string infos; |
| OverlayInfoRegistry::RetrieveAndClear(&infos); |
| |
| if (infos.empty()) { |
| image_node->source = NULL; |
| return; |
| } |
| |
| auto qrcode = QrCode::encodeText(infos.c_str(), QrCode::Ecc::LOW, |
| kMinimumQrCodeVersion); |
| image_node->source = |
| CreateImageForQrCode(qrcode, screen_size, slots, resource_provider); |
| auto image_size = image_node->source->GetSize(); |
| image_node->destination_rect = math::RectF( |
| screen_size.width() - image_size.width() - kScreenMarginInPixels, |
| screen_size.height() - image_size.height() - kScreenMarginInPixels, |
| image_size.width(), image_size.height()); |
| } |
| |
| } // namespace |
| |
| QrCodeOverlay::QrCodeOverlay( |
| const math::Size& screen_size, int slots, |
| ResourceProvider* resource_provider, |
| const RenderTreeProducedCB& render_tree_produced_cb) |
| : render_tree_produced_cb_(render_tree_produced_cb), |
| slots_(slots), |
| screen_size_(screen_size), |
| resource_provider_(resource_provider) { |
| DCHECK_GT(screen_size.width(), 0); |
| DCHECK_GT(screen_size.height(), 0); |
| DCHECK(!render_tree_produced_cb_.is_null()); |
| |
| UpdateRenderTree(); |
| } |
| |
| void QrCodeOverlay::SetSize(const math::Size& size) { |
| DCHECK_GT(size.width(), 0); |
| DCHECK_GT(size.height(), 0); |
| |
| screen_size_ = size; |
| UpdateRenderTree(); |
| } |
| |
| void QrCodeOverlay::SetResourceProvider(ResourceProvider* resource_provider) { |
| resource_provider_ = resource_provider; |
| UpdateRenderTree(); |
| } |
| |
| void QrCodeOverlay::UpdateRenderTree() { |
| TRACE_EVENT0("cobalt::overlay_info", "QrCodeOverlay::UpdateRenderTree()"); |
| |
| if (resource_provider_ == NULL) { |
| return; |
| } |
| |
| scoped_refptr<ImageNode> image_node = new ImageNode(nullptr); |
| render_tree::animations::AnimateNode::Builder animate_node_builder; |
| |
| animate_node_builder.Add(image_node, base::Bind(AnimateCB, screen_size_, |
| slots_, resource_provider_)); |
| |
| render_tree_produced_cb_.Run(new render_tree::animations::AnimateNode( |
| animate_node_builder, image_node)); |
| } |
| |
| } // namespace overlay_info |
| } // namespace cobalt |