| // Copyright 2018 Google Inc. 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 <vector> |
| |
| #include "base/compiler_specific.h" |
| #include "base/debug/trace_event.h" |
| #include "base/logging.h" |
| #include "cobalt/overlay_info/overlay_info_registry.h" |
| #include "cobalt/render_tree/animations/animate_node.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; |
| |
| const int kModuleDimensionInPixels = 4; |
| const int kPixelSizeInBytes = 4; |
| const uint32_t kBlack = 0x00000000; |
| const uint32_t kWhite = 0xFFFFFFFF; |
| const uint32_t kBorderColor = kWhite; |
| const int kCodeBorderInPixels = 16; |
| const int kScreenMarginInPixels = 128; |
| |
| int64_t s_frame_count_ = 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 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(kModuleDimensionInPixels, kModuleDimensionInPixels, |
| pitch_in_bytes, qr_code.getModule(row, column) ? kBlack : kWhite, |
| column_data); |
| column_data += kPixelSizeInBytes * kModuleDimensionInPixels; |
| } |
| |
| row_data += pitch_in_bytes * kModuleDimensionInPixels; |
| } |
| } |
| |
| scoped_refptr<Image> CreateImageForQrCodes( |
| const std::vector<QrCode>& qr_codes, const math::Size& screen_size, |
| render_tree::ResourceProvider* resource_provider) { |
| TRACE_EVENT0("cobalt::overlay_info", "CreateImageForQrCodes()"); |
| |
| int max_code_size = 0; |
| |
| for (auto& qr_code : qr_codes) { |
| max_code_size = std::max(max_code_size, qr_code.getSize()); |
| } |
| |
| int column = |
| (screen_size.width() - kScreenMarginInPixels * 2 - kCodeBorderInPixels) / |
| (max_code_size * kModuleDimensionInPixels + kCodeBorderInPixels); |
| column = std::min(column, static_cast<int>(qr_codes.size())); |
| int row = (static_cast<int>(qr_codes.size()) + column - 1) / column; |
| |
| int image_width = column * max_code_size * kModuleDimensionInPixels + |
| kCodeBorderInPixels * (column + 1); |
| int image_height = row * max_code_size * kModuleDimensionInPixels + |
| kCodeBorderInPixels * (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(); |
| |
| size_t qr_code_index = 0; |
| auto row_data = image_data->GetMemory(); |
| for (int i = 0; i < row; ++i) { |
| // Draw the top border of all qr codes in the row. |
| DrawRect(image_width, kCodeBorderInPixels, image_desc.pitch_in_bytes, |
| kBorderColor, row_data); |
| row_data += kCodeBorderInPixels * image_desc.pitch_in_bytes; |
| auto column_data = row_data; |
| |
| for (int j = 0; j < column; ++j) { |
| // Draw the left border. |
| DrawRect(kCodeBorderInPixels, max_code_size * kModuleDimensionInPixels, |
| image_desc.pitch_in_bytes, kBorderColor, column_data); |
| column_data += kCodeBorderInPixels * kPixelSizeInBytes; |
| if (qr_code_index < qr_codes.size()) { |
| // Draw qr code. |
| DrawQrCode(qr_codes[qr_code_index], image_desc.pitch_in_bytes, |
| column_data); |
| ++qr_code_index; |
| } |
| column_data += |
| max_code_size * kModuleDimensionInPixels * kPixelSizeInBytes; |
| } |
| |
| // Draw the right border of the row. |
| DrawRect(kCodeBorderInPixels, max_code_size * kModuleDimensionInPixels, |
| image_desc.pitch_in_bytes, kBorderColor, column_data); |
| |
| row_data += |
| max_code_size * kModuleDimensionInPixels * image_desc.pitch_in_bytes; |
| } |
| |
| // Draw the bottom border of all qr code. |
| DrawRect(image_width, kCodeBorderInPixels, image_desc.pitch_in_bytes, |
| kBorderColor, row_data); |
| |
| return resource_provider->CreateImage(image_data.Pass()); |
| } |
| |
| void AnimateCB(math::Size screen_size, |
| render_tree::ResourceProvider* resource_provider, |
| render_tree::ImageNode::Builder* image_node, |
| base::TimeDelta time) { |
| UNREFERENCED_PARAMETER(time); |
| DCHECK(image_node); |
| |
| TRACE_EVENT0("cobalt::overlay_info", "AnimateCB()"); |
| |
| OverlayInfoRegistry::Register("overlay_info:frame_count", &s_frame_count_, |
| sizeof(s_frame_count_)); |
| ++s_frame_count_; |
| |
| std::vector<uint8_t> infos; |
| OverlayInfoRegistry::RetrieveAndClear(&infos); |
| |
| if (infos.empty()) { |
| image_node->source = NULL; |
| return; |
| } |
| |
| // Use a vector in case we decide to switch back to multiple qr codes. |
| std::vector<QrCode> qr_codes; |
| qr_codes.emplace_back(QrCode::encodeBinary(infos, QrCode::Ecc::LOW)); |
| |
| image_node->source = |
| CreateImageForQrCodes(qr_codes, screen_size, resource_provider); |
| auto image_size = image_node->source->GetSize(); |
| // TODO: Move the QR code between draws to avoid tearing. |
| image_node->destination_rect = |
| math::RectF(kScreenMarginInPixels, kScreenMarginInPixels, |
| image_size.width(), image_size.height()); |
| } |
| |
| } // namespace |
| |
| QrCodeOverlay::QrCodeOverlay( |
| const math::Size& screen_size, |
| render_tree::ResourceProvider* resource_provider, |
| const RenderTreeProducedCB& render_tree_produced_cb) |
| : render_tree_produced_cb_(render_tree_produced_cb), |
| 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( |
| render_tree::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(NULL); |
| render_tree::animations::AnimateNode::Builder animate_node_builder; |
| |
| animate_node_builder.Add( |
| image_node, base::Bind(AnimateCB, screen_size_, resource_provider_)); |
| |
| render_tree_produced_cb_.Run(new render_tree::animations::AnimateNode( |
| animate_node_builder, image_node)); |
| } |
| |
| } // namespace overlay_info |
| } // namespace cobalt |