1// Copyright 2014 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 <algorithm>
6
7#include "base/memory/scoped_ptr.h"
8#include "base/message_loop/message_loop.h"
9#include "base/strings/string_tokenizer.h"
10#include "mojo/application/application_runner_chromium.h"
11#include "mojo/examples/media_viewer/media_viewer.mojom.h"
12#include "mojo/public/c/system/main.h"
13#include "mojo/public/cpp/application/application_connection.h"
14#include "mojo/public/cpp/application/application_delegate.h"
15#include "mojo/public/cpp/application/application_impl.h"
16#include "mojo/public/cpp/application/interface_factory_impl.h"
17#include "mojo/public/cpp/application/service_provider_impl.h"
18#include "mojo/services/public/cpp/view_manager/types.h"
19#include "mojo/services/public/cpp/view_manager/view.h"
20#include "mojo/services/public/cpp/view_manager/view_manager.h"
21#include "mojo/services/public/cpp/view_manager/view_manager_client_factory.h"
22#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h"
23#include "mojo/services/public/cpp/view_manager/view_observer.h"
24#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
25#include "skia/ext/platform_canvas.h"
26#include "skia/ext/refptr.h"
27#include "third_party/skia/include/core/SkBitmap.h"
28#include "third_party/skia/include/core/SkCanvas.h"
29#include "third_party/skia/include/core/SkPaint.h"
30#include "third_party/skia/include/core/SkScalar.h"
31#include "ui/gfx/codec/png_codec.h"
32
33namespace mojo {
34namespace examples {
35
36class PNGViewer;
37
38// TODO(aa): Hook up ZoomableMedia interface again.
39class PNGView : public ViewManagerDelegate, public ViewObserver {
40 public:
41  static void Spawn(URLResponsePtr response,
42                    ServiceProviderImpl* exported_services,
43                    scoped_ptr<ServiceProvider> imported_services,
44                    Shell* shell) {
45    // PNGView deletes itself when its View is destroyed.
46    new PNGView(
47        response.Pass(), exported_services, imported_services.Pass(), shell);
48  }
49
50 private:
51  static const uint16_t kMaxZoomPercentage = 400;
52  static const uint16_t kMinZoomPercentage = 20;
53  static const uint16_t kDefaultZoomPercentage = 100;
54  static const uint16_t kZoomStep = 20;
55
56  PNGView(URLResponsePtr response,
57          ServiceProviderImpl* exported_services,
58          scoped_ptr<ServiceProvider> imported_services,
59          Shell* shell)
60      : imported_services_(imported_services.Pass()),
61        root_(NULL),
62        view_manager_client_factory_(shell, this),
63        zoom_percentage_(kDefaultZoomPercentage) {
64    exported_services->AddService(&view_manager_client_factory_);
65    DecodePNG(response.Pass());
66  }
67
68  virtual ~PNGView() {
69    if (root_)
70      root_->RemoveObserver(this);
71  }
72
73  // Overridden from ViewManagerDelegate:
74  virtual void OnEmbed(ViewManager* view_manager,
75                       View* root,
76                       ServiceProviderImpl* exported_services,
77                       scoped_ptr<ServiceProvider> imported_services) OVERRIDE {
78    root_ = root;
79    root_->AddObserver(this);
80    root_->SetColor(SK_ColorGRAY);
81    if (!bitmap_.isNull())
82      DrawBitmap();
83  }
84
85  virtual void OnViewManagerDisconnected(ViewManager* view_manager) OVERRIDE {
86    // TODO(aa): Need to figure out how shutdown works.
87  }
88
89  // Overridden from ViewObserver:
90  virtual void OnViewBoundsChanged(View* view,
91                                   const gfx::Rect& old_bounds,
92                                   const gfx::Rect& new_bounds) OVERRIDE {
93    DCHECK_EQ(view, root_);
94    DrawBitmap();
95  }
96
97  virtual void OnViewDestroyed(View* view) OVERRIDE {
98    DCHECK_EQ(view, root_);
99    delete this;
100  }
101
102  void DecodePNG(URLResponsePtr response) {
103    int content_length = GetContentLength(response->headers);
104    scoped_ptr<unsigned char[]> data(new unsigned char[content_length]);
105    unsigned char* buf = data.get();
106    uint32_t bytes_remaining = content_length;
107    uint32_t num_bytes = bytes_remaining;
108    while (bytes_remaining > 0) {
109      MojoResult result = ReadDataRaw(
110          response->body.get(), buf, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
111      if (result == MOJO_RESULT_SHOULD_WAIT) {
112        Wait(response->body.get(),
113             MOJO_HANDLE_SIGNAL_READABLE,
114             MOJO_DEADLINE_INDEFINITE);
115      } else if (result == MOJO_RESULT_OK) {
116        buf += num_bytes;
117        num_bytes = bytes_remaining -= num_bytes;
118      } else {
119        break;
120      }
121    }
122
123    gfx::PNGCodec::Decode(static_cast<const unsigned char*>(data.get()),
124                          content_length,
125                          &bitmap_);
126  }
127
128  void DrawBitmap() {
129    if (!root_)
130      return;
131
132    skia::RefPtr<SkCanvas> canvas(skia::AdoptRef(skia::CreatePlatformCanvas(
133        root_->bounds().width(), root_->bounds().height(), true)));
134    canvas->drawColor(SK_ColorGRAY);
135    SkPaint paint;
136    SkScalar scale =
137        SkFloatToScalar(zoom_percentage_ * 1.0f / kDefaultZoomPercentage);
138    canvas->scale(scale, scale);
139    canvas->drawBitmap(bitmap_, 0, 0, &paint);
140    root_->SetContents(skia::GetTopDevice(*canvas)->accessBitmap(true));
141  }
142
143  void ZoomIn() {
144    if (zoom_percentage_ >= kMaxZoomPercentage)
145      return;
146    zoom_percentage_ += kZoomStep;
147    DrawBitmap();
148  }
149
150  void ZoomOut() {
151    if (zoom_percentage_ <= kMinZoomPercentage)
152      return;
153    zoom_percentage_ -= kZoomStep;
154    DrawBitmap();
155  }
156
157  void ZoomToActualSize() {
158    if (zoom_percentage_ == kDefaultZoomPercentage)
159      return;
160    zoom_percentage_ = kDefaultZoomPercentage;
161    DrawBitmap();
162  }
163
164  int GetContentLength(const Array<String>& headers) {
165    for (size_t i = 0; i < headers.size(); ++i) {
166      base::StringTokenizer t(headers[i], ": ;=");
167      while (t.GetNext()) {
168        if (!t.token_is_delim() && t.token() == "Content-Length") {
169          while (t.GetNext()) {
170            if (!t.token_is_delim())
171              return atoi(t.token().c_str());
172          }
173        }
174      }
175    }
176    return 0;
177  }
178
179  SkBitmap bitmap_;
180  scoped_ptr<ServiceProvider> imported_services_;
181  View* root_;
182  ViewManagerClientFactory view_manager_client_factory_;
183  uint16_t zoom_percentage_;
184
185  DISALLOW_COPY_AND_ASSIGN(PNGView);
186};
187
188class ContentHandlerImpl : public InterfaceImpl<ContentHandler> {
189 public:
190  explicit ContentHandlerImpl(Shell* shell) : shell_(shell) {}
191  virtual ~ContentHandlerImpl() {}
192
193 private:
194  // Overridden from ContentHandler:
195  virtual void OnConnect(
196      const mojo::String& url,
197      URLResponsePtr response,
198      InterfaceRequest<ServiceProvider> service_provider) OVERRIDE {
199    ServiceProviderImpl* exported_services = new ServiceProviderImpl();
200    BindToRequest(exported_services, &service_provider);
201    scoped_ptr<ServiceProvider> remote(
202        exported_services->CreateRemoteServiceProvider());
203    PNGView::Spawn(response.Pass(), exported_services, remote.Pass(), shell_);
204  }
205
206  Shell* shell_;
207
208  DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl);
209};
210
211class PNGViewer : public ApplicationDelegate {
212 public:
213  PNGViewer() {}
214 private:
215  // Overridden from ApplicationDelegate:
216  virtual void Initialize(ApplicationImpl* app) MOJO_OVERRIDE {
217    content_handler_factory_.reset(
218        new InterfaceFactoryImplWithContext<ContentHandlerImpl, Shell>(
219            app->shell()));
220  }
221
222  // Overridden from ApplicationDelegate:
223  virtual bool ConfigureIncomingConnection(ApplicationConnection* connection)
224      MOJO_OVERRIDE {
225    connection->AddService(content_handler_factory_.get());
226    return true;
227  }
228
229  scoped_ptr<InterfaceFactoryImplWithContext<ContentHandlerImpl, Shell> >
230      content_handler_factory_;
231
232  DISALLOW_COPY_AND_ASSIGN(PNGViewer);
233};
234
235}  // namespace examples
236}  // namespace mojo
237
238MojoResult MojoMain(MojoHandle shell_handle) {
239  mojo::ApplicationRunnerChromium runner(new mojo::examples::PNGViewer);
240  return runner.Run(shell_handle);
241}
242