]> rtime.felk.cvut.cz Git - hornmich/skoda-qr-demo.git/blob - QRScanner/mobile/src/main/java/cz/cvut/fel/dce/qrscanner/mupdf/PageView.java
Add MuPDF native source codes
[hornmich/skoda-qr-demo.git] / QRScanner / mobile / src / main / java / cz / cvut / fel / dce / qrscanner / mupdf / PageView.java
1 package cz.cvut.fel.dce.qrscanner.mupdf;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5
6 import android.content.Context;
7 import android.graphics.Bitmap;
8 import android.graphics.Bitmap.Config;
9 import android.graphics.Canvas;
10 import android.graphics.Color;
11 import android.graphics.Matrix;
12 import android.graphics.Paint;
13 import android.graphics.Path;
14 import android.graphics.Point;
15 import android.graphics.PointF;
16 import android.graphics.Rect;
17 import android.graphics.RectF;
18 import android.os.Handler;
19 import android.view.View;
20 import android.view.ViewGroup;
21 import android.widget.ImageView;
22 import android.widget.ProgressBar;
23
24 import cz.cvut.fel.dce.qrscanner.R;
25
26 // Make our ImageViews opaque to optimize redraw
27 class OpaqueImageView extends ImageView {
28
29         public OpaqueImageView(Context context) {
30                 super(context);
31         }
32
33         @Override
34         public boolean isOpaque() {
35                 return true;
36         }
37 }
38
39 interface TextProcessor {
40         void onStartLine();
41         void onWord(TextWord word);
42         void onEndLine();
43 }
44
45 class TextSelector {
46         final private TextWord[][] mText;
47         final private RectF mSelectBox;
48
49         public TextSelector(TextWord[][] text, RectF selectBox) {
50                 mText = text;
51                 mSelectBox = selectBox;
52         }
53
54         public void select(TextProcessor tp) {
55                 if (mText == null || mSelectBox == null)
56                         return;
57
58                 ArrayList<TextWord[]> lines = new ArrayList<TextWord[]>();
59                 for (TextWord[] line : mText)
60                         if (line[0].bottom > mSelectBox.top && line[0].top < mSelectBox.bottom)
61                                 lines.add(line);
62
63                 Iterator<TextWord[]> it = lines.iterator();
64                 while (it.hasNext()) {
65                         TextWord[] line = it.next();
66                         boolean firstLine = line[0].top < mSelectBox.top;
67                         boolean lastLine = line[0].bottom > mSelectBox.bottom;
68                         float start = Float.NEGATIVE_INFINITY;
69                         float end = Float.POSITIVE_INFINITY;
70
71                         if (firstLine && lastLine) {
72                                 start = Math.min(mSelectBox.left, mSelectBox.right);
73                                 end = Math.max(mSelectBox.left, mSelectBox.right);
74                         } else if (firstLine) {
75                                 start = mSelectBox.left;
76                         } else if (lastLine) {
77                                 end = mSelectBox.right;
78                         }
79
80                         tp.onStartLine();
81
82                         for (TextWord word : line)
83                                 if (word.right > start && word.left < end)
84                                         tp.onWord(word);
85
86                         tp.onEndLine();
87                 }
88         }
89 }
90
91 public abstract class PageView extends ViewGroup {
92         private static final int HIGHLIGHT_COLOR = 0x802572AC;
93         private static final int LINK_COLOR = 0x80AC7225;
94         private static final int BOX_COLOR = 0xFF4444FF;
95         private static final int INK_COLOR = 0xFFFF0000;
96         private static final float INK_THICKNESS = 10.0f;
97         private static final int BACKGROUND_COLOR = 0xFFFFFFFF;
98         private static final int PROGRESS_DIALOG_DELAY = 200;
99         protected final Context   mContext;
100         protected     int       mPageNumber;
101         private       Point     mParentSize;
102         protected     Point     mSize;   // Size of page at minimum zoom
103         protected     float     mSourceScale;
104
105         private       ImageView mEntire; // Image rendered at minimum zoom
106         private       Bitmap    mEntireBm;
107         private       Matrix    mEntireMat;
108         private       AsyncTask<Void,Void,TextWord[][]> mGetText;
109         private       AsyncTask<Void,Void,LinkInfo[]> mGetLinkInfo;
110         private       CancellableAsyncTask<Void, Void> mDrawEntire;
111
112         private       Point     mPatchViewSize; // View size on the basis of which the patch was created
113         private       Rect      mPatchArea;
114         private       ImageView mPatch;
115         private       Bitmap    mPatchBm;
116         private       CancellableAsyncTask<Void,Void> mDrawPatch;
117         private       RectF     mSearchBoxes[];
118         protected     LinkInfo  mLinks[];
119         private       RectF     mSelectBox;
120         private       TextWord  mText[][];
121         private       RectF     mItemSelectBox;
122         protected     ArrayList<ArrayList<PointF>> mDrawing;
123         private       View      mSearchView;
124         private       boolean   mIsBlank;
125         private       boolean   mHighlightLinks;
126
127         private       ProgressBar mBusyIndicator;
128         private final Handler   mHandler = new Handler();
129
130         public PageView(Context c, Point parentSize, Bitmap sharedHqBm) {
131                 super(c);
132                 mContext    = c;
133                 mParentSize = parentSize;
134                 setBackgroundColor(BACKGROUND_COLOR);
135                 mEntireBm = Bitmap.createBitmap(parentSize.x, parentSize.y, Config.ARGB_8888);
136                 mPatchBm = sharedHqBm;
137                 mEntireMat = new Matrix();
138         }
139
140         protected abstract CancellableTaskDefinition<Void, Void> getDrawPageTask(Bitmap bm, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight);
141         protected abstract CancellableTaskDefinition<Void, Void> getUpdatePageTask(Bitmap bm, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight);
142         protected abstract LinkInfo[] getLinkInfo();
143         protected abstract TextWord[][] getText();
144         protected abstract void addMarkup(PointF[] quadPoints, Annotation.Type type);
145
146         private void reinit() {
147                 // Cancel pending render task
148                 if (mDrawEntire != null) {
149                         mDrawEntire.cancelAndWait();
150                         mDrawEntire = null;
151                 }
152
153                 if (mDrawPatch != null) {
154                         mDrawPatch.cancelAndWait();
155                         mDrawPatch = null;
156                 }
157
158                 if (mGetLinkInfo != null) {
159                         mGetLinkInfo.cancel(true);
160                         mGetLinkInfo = null;
161                 }
162
163                 if (mGetText != null) {
164                         mGetText.cancel(true);
165                         mGetText = null;
166                 }
167
168                 mIsBlank = true;
169                 mPageNumber = 0;
170
171                 if (mSize == null)
172                         mSize = mParentSize;
173
174                 if (mEntire != null) {
175                         mEntire.setImageBitmap(null);
176                         mEntire.invalidate();
177                 }
178
179                 if (mPatch != null) {
180                         mPatch.setImageBitmap(null);
181                         mPatch.invalidate();
182                 }
183
184                 mPatchViewSize = null;
185                 mPatchArea = null;
186
187                 mSearchBoxes = null;
188                 mLinks = null;
189                 mSelectBox = null;
190                 mText = null;
191                 mItemSelectBox = null;
192         }
193
194         public void releaseResources() {
195                 reinit();
196
197                 if (mBusyIndicator != null) {
198                         removeView(mBusyIndicator);
199                         mBusyIndicator = null;
200                 }
201         }
202
203         public void releaseBitmaps() {
204                 reinit();
205                 mEntireBm = null;
206                 mPatchBm = null;
207         }
208
209         public void blank(int page) {
210                 reinit();
211                 mPageNumber = page;
212
213                 if (mBusyIndicator == null) {
214                         mBusyIndicator = new ProgressBar(mContext);
215                         mBusyIndicator.setIndeterminate(true);
216                         mBusyIndicator.setBackgroundResource(R.drawable.busy);
217                         addView(mBusyIndicator);
218                 }
219
220                 setBackgroundColor(BACKGROUND_COLOR);
221         }
222
223         public void setPage(int page, PointF size) {
224                 // Cancel pending render task
225                 if (mDrawEntire != null) {
226                         mDrawEntire.cancelAndWait();
227                         mDrawEntire = null;
228                 }
229
230                 mIsBlank = false;
231                 // Highlights may be missing because mIsBlank was true on last draw
232                 if (mSearchView != null)
233                         mSearchView.invalidate();
234
235                 mPageNumber = page;
236                 if (mEntire == null) {
237                         mEntire = new OpaqueImageView(mContext);
238                         mEntire.setScaleType(ImageView.ScaleType.MATRIX);
239                         addView(mEntire);
240                 }
241
242                 // Calculate scaled size that fits within the screen limits
243                 // This is the size at minimum zoom
244                 mSourceScale = Math.min(mParentSize.x/size.x, mParentSize.y/size.y);
245                 Point newSize = new Point((int)(size.x*mSourceScale), (int)(size.y*mSourceScale));
246                 mSize = newSize;
247
248                 mEntire.setImageBitmap(null);
249                 mEntire.invalidate();
250
251                 // Get the link info in the background
252                 mGetLinkInfo = new AsyncTask<Void,Void,LinkInfo[]>() {
253                         protected LinkInfo[] doInBackground(Void... v) {
254                                 return getLinkInfo();
255                         }
256
257                         protected void onPostExecute(LinkInfo[] v) {
258                                 mLinks = v;
259                                 if (mSearchView != null)
260                                         mSearchView.invalidate();
261                         }
262                 };
263
264                 mGetLinkInfo.execute();
265
266                 // Render the page in the background
267                 mDrawEntire = new CancellableAsyncTask<Void, Void>(getDrawPageTask(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y)) {
268
269                         @Override
270                         public void onPreExecute() {
271                                 setBackgroundColor(BACKGROUND_COLOR);
272                                 mEntire.setImageBitmap(null);
273                                 mEntire.invalidate();
274
275                                 if (mBusyIndicator == null) {
276                                         mBusyIndicator = new ProgressBar(mContext);
277                                         mBusyIndicator.setIndeterminate(true);
278                                         mBusyIndicator.setBackgroundResource(R.drawable.busy);
279                                         addView(mBusyIndicator);
280                                         mBusyIndicator.setVisibility(INVISIBLE);
281                                         mHandler.postDelayed(new Runnable() {
282                                                 public void run() {
283                                                         if (mBusyIndicator != null)
284                                                                 mBusyIndicator.setVisibility(VISIBLE);
285                                                 }
286                                         }, PROGRESS_DIALOG_DELAY);
287                                 }
288                         }
289
290                         @Override
291                         public void onPostExecute(Void result) {
292                                 removeView(mBusyIndicator);
293                                 mBusyIndicator = null;
294                                 mEntire.setImageBitmap(mEntireBm);
295                                 mEntire.invalidate();
296                                 setBackgroundColor(Color.TRANSPARENT);
297
298                         }
299                 };
300
301                 mDrawEntire.execute();
302
303                 if (mSearchView == null) {
304                         mSearchView = new View(mContext) {
305                                 @Override
306                                 protected void onDraw(final Canvas canvas) {
307                                         super.onDraw(canvas);
308                                         // Work out current total scale factor
309                                         // from source to view
310                                         final float scale = mSourceScale*(float)getWidth()/(float)mSize.x;
311                                         final Paint paint = new Paint();
312
313                                         if (!mIsBlank && mSearchBoxes != null) {
314                                                 paint.setColor(HIGHLIGHT_COLOR);
315                                                 for (RectF rect : mSearchBoxes)
316                                                         canvas.drawRect(rect.left*scale, rect.top*scale,
317                                                                                 rect.right*scale, rect.bottom*scale,
318                                                                                 paint);
319                                         }
320
321                                         if (!mIsBlank && mLinks != null && mHighlightLinks) {
322                                                 paint.setColor(LINK_COLOR);
323                                                 for (LinkInfo link : mLinks)
324                                                         canvas.drawRect(link.rect.left*scale, link.rect.top*scale,
325                                                                                 link.rect.right*scale, link.rect.bottom*scale,
326                                                                                 paint);
327                                         }
328
329                                         if (mSelectBox != null && mText != null) {
330                                                 paint.setColor(HIGHLIGHT_COLOR);
331                                                 processSelectedText(new TextProcessor() {
332                                                         RectF rect;
333
334                                                         public void onStartLine() {
335                                                                 rect = new RectF();
336                                                         }
337
338                                                         public void onWord(TextWord word) {
339                                                                 rect.union(word);
340                                                         }
341
342                                                         public void onEndLine() {
343                                                                 if (!rect.isEmpty())
344                                                                         canvas.drawRect(rect.left*scale, rect.top*scale, rect.right*scale, rect.bottom*scale, paint);
345                                                         }
346                                                 });
347                                         }
348
349                                         if (mItemSelectBox != null) {
350                                                 paint.setStyle(Paint.Style.STROKE);
351                                                 paint.setColor(BOX_COLOR);
352                                                 canvas.drawRect(mItemSelectBox.left*scale, mItemSelectBox.top*scale, mItemSelectBox.right*scale, mItemSelectBox.bottom*scale, paint);
353                                         }
354
355                                         if (mDrawing != null) {
356                                                 Path path = new Path();
357                                                 PointF p;
358
359                                                 paint.setAntiAlias(true);
360                                                 paint.setDither(true);
361                                                 paint.setStrokeJoin(Paint.Join.ROUND);
362                                                 paint.setStrokeCap(Paint.Cap.ROUND);
363
364                                                 paint.setStyle(Paint.Style.FILL);
365                                                 paint.setStrokeWidth(INK_THICKNESS * scale);
366                                                 paint.setColor(INK_COLOR);
367
368                                                 Iterator<ArrayList<PointF>> it = mDrawing.iterator();
369                                                 while (it.hasNext()) {
370                                                         ArrayList<PointF> arc = it.next();
371                                                         if (arc.size() >= 2) {
372                                                                 Iterator<PointF> iit = arc.iterator();
373                                                                 p = iit.next();
374                                                                 float mX = p.x * scale;
375                                                                 float mY = p.y * scale;
376                                                                 path.moveTo(mX, mY);
377                                                                 while (iit.hasNext()) {
378                                                                         p = iit.next();
379                                                                         float x = p.x * scale;
380                                                                         float y = p.y * scale;
381                                                                         path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
382                                                                         mX = x;
383                                                                         mY = y;
384                                                                 }
385                                                                 path.lineTo(mX, mY);
386                                                         } else {
387                                                                 p = arc.get(0);
388                                                                 canvas.drawCircle(p.x * scale, p.y * scale, INK_THICKNESS * scale / 2, paint);
389                                                         }
390                                                 }
391
392                                                 paint.setStyle(Paint.Style.STROKE);
393                                                 canvas.drawPath(path, paint);
394                                         }
395                                 }
396                         };
397
398                         addView(mSearchView);
399                 }
400                 requestLayout();
401         }
402
403         public void setSearchBoxes(RectF searchBoxes[]) {
404                 mSearchBoxes = searchBoxes;
405                 if (mSearchView != null)
406                         mSearchView.invalidate();
407         }
408
409         public void setLinkHighlighting(boolean f) {
410                 mHighlightLinks = f;
411                 if (mSearchView != null)
412                         mSearchView.invalidate();
413         }
414
415         public void deselectText() {
416                 mSelectBox = null;
417                 mSearchView.invalidate();
418         }
419
420         public void selectText(float x0, float y0, float x1, float y1) {
421                 float scale = mSourceScale*(float)getWidth()/(float)mSize.x;
422                 float docRelX0 = (x0 - getLeft())/scale;
423                 float docRelY0 = (y0 - getTop())/scale;
424                 float docRelX1 = (x1 - getLeft())/scale;
425                 float docRelY1 = (y1 - getTop())/scale;
426                 // Order on Y but maintain the point grouping
427                 if (docRelY0 <= docRelY1)
428                         mSelectBox = new RectF(docRelX0, docRelY0, docRelX1, docRelY1);
429                 else
430                         mSelectBox = new RectF(docRelX1, docRelY1, docRelX0, docRelY0);
431
432                 mSearchView.invalidate();
433
434                 if (mGetText == null) {
435                         mGetText = new AsyncTask<Void,Void,TextWord[][]>() {
436                                 @Override
437                                 protected TextWord[][] doInBackground(Void... params) {
438                                         return getText();
439                                 }
440                                 @Override
441                                 protected void onPostExecute(TextWord[][] result) {
442                                         mText = result;
443                                         mSearchView.invalidate();
444                                 }
445                         };
446
447                         mGetText.execute();
448                 }
449         }
450
451         public void startDraw(float x, float y) {
452                 float scale = mSourceScale*(float)getWidth()/(float)mSize.x;
453                 float docRelX = (x - getLeft())/scale;
454                 float docRelY = (y - getTop())/scale;
455                 if (mDrawing == null)
456                         mDrawing = new ArrayList<ArrayList<PointF>>();
457
458                 ArrayList<PointF> arc = new ArrayList<PointF>();
459                 arc.add(new PointF(docRelX, docRelY));
460                 mDrawing.add(arc);
461                 mSearchView.invalidate();
462         }
463
464         public void continueDraw(float x, float y) {
465                 float scale = mSourceScale*(float)getWidth()/(float)mSize.x;
466                 float docRelX = (x - getLeft())/scale;
467                 float docRelY = (y - getTop())/scale;
468
469                 if (mDrawing != null && mDrawing.size() > 0) {
470                         ArrayList<PointF> arc = mDrawing.get(mDrawing.size() - 1);
471                         arc.add(new PointF(docRelX, docRelY));
472                         mSearchView.invalidate();
473                 }
474         }
475
476         public void cancelDraw() {
477                 mDrawing = null;
478                 mSearchView.invalidate();
479         }
480
481         protected PointF[][] getDraw() {
482                 if (mDrawing == null)
483                         return null;
484
485                 PointF[][] path = new PointF[mDrawing.size()][];
486
487                 for (int i = 0; i < mDrawing.size(); i++) {
488                         ArrayList<PointF> arc = mDrawing.get(i);
489                         path[i] = arc.toArray(new PointF[arc.size()]);
490                 }
491
492                 return path;
493         }
494
495         protected void processSelectedText(TextProcessor tp) {
496                 (new TextSelector(mText, mSelectBox)).select(tp);
497         }
498
499         public void setItemSelectBox(RectF rect) {
500                 mItemSelectBox = rect;
501                 if (mSearchView != null)
502                         mSearchView.invalidate();
503         }
504
505         @Override
506         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
507                 int x, y;
508                 switch(View.MeasureSpec.getMode(widthMeasureSpec)) {
509                 case View.MeasureSpec.UNSPECIFIED:
510                         x = mSize.x;
511                         break;
512                 default:
513                         x = View.MeasureSpec.getSize(widthMeasureSpec);
514                 }
515                 switch(View.MeasureSpec.getMode(heightMeasureSpec)) {
516                 case View.MeasureSpec.UNSPECIFIED:
517                         y = mSize.y;
518                         break;
519                 default:
520                         y = View.MeasureSpec.getSize(heightMeasureSpec);
521                 }
522
523                 setMeasuredDimension(x, y);
524
525                 if (mBusyIndicator != null) {
526                         int limit = Math.min(mParentSize.x, mParentSize.y)/2;
527                         mBusyIndicator.measure(View.MeasureSpec.AT_MOST | limit, View.MeasureSpec.AT_MOST | limit);
528                 }
529         }
530
531         @Override
532         protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
533                 int w  = right-left;
534                 int h = bottom-top;
535
536                 if (mEntire != null) {
537                         if (mEntire.getWidth() != w || mEntire.getHeight() != h) {
538                                 mEntireMat.setScale(w/(float)mSize.x, h/(float)mSize.y);
539                                 mEntire.setImageMatrix(mEntireMat);
540                                 mEntire.invalidate();
541                         }
542                         mEntire.layout(0, 0, w, h);
543                 }
544
545                 if (mSearchView != null) {
546                         mSearchView.layout(0, 0, w, h);
547                 }
548
549                 if (mPatchViewSize != null) {
550                         if (mPatchViewSize.x != w || mPatchViewSize.y != h) {
551                                 // Zoomed since patch was created
552                                 mPatchViewSize = null;
553                                 mPatchArea     = null;
554                                 if (mPatch != null) {
555                                         mPatch.setImageBitmap(null);
556                                         mPatch.invalidate();
557                                 }
558                         } else {
559                                 mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom);
560                         }
561                 }
562
563                 if (mBusyIndicator != null) {
564                         int bw = mBusyIndicator.getMeasuredWidth();
565                         int bh = mBusyIndicator.getMeasuredHeight();
566
567                         mBusyIndicator.layout((w-bw)/2, (h-bh)/2, (w+bw)/2, (h+bh)/2);
568                 }
569         }
570
571         public void updateHq(boolean update) {
572                 Rect viewArea = new Rect(getLeft(),getTop(),getRight(),getBottom());
573                 if (viewArea.width() == mSize.x || viewArea.height() == mSize.y) {
574                         // If the viewArea's size matches the unzoomed size, there is no need for an hq patch
575                         if (mPatch != null) {
576                                 mPatch.setImageBitmap(null);
577                                 mPatch.invalidate();
578                         }
579                 } else {
580                         final Point patchViewSize = new Point(viewArea.width(), viewArea.height());
581                         final Rect patchArea = new Rect(0, 0, mParentSize.x, mParentSize.y);
582
583                         // Intersect and test that there is an intersection
584                         if (!patchArea.intersect(viewArea))
585                                 return;
586
587                         // Offset patch area to be relative to the view top left
588                         patchArea.offset(-viewArea.left, -viewArea.top);
589
590                         boolean area_unchanged = patchArea.equals(mPatchArea) && patchViewSize.equals(mPatchViewSize);
591
592                         // If being asked for the same area as last time and not because of an update then nothing to do
593                         if (area_unchanged && !update)
594                                 return;
595
596                         boolean completeRedraw = !(area_unchanged && update);
597
598                         // Stop the drawing of previous patch if still going
599                         if (mDrawPatch != null) {
600                                 mDrawPatch.cancelAndWait();
601                                 mDrawPatch = null;
602                         }
603
604                         // Create and add the image view if not already done
605                         if (mPatch == null) {
606                                 mPatch = new OpaqueImageView(mContext);
607                                 mPatch.setScaleType(ImageView.ScaleType.MATRIX);
608                                 addView(mPatch);
609                                 mSearchView.bringToFront();
610                         }
611
612                         CancellableTaskDefinition<Void, Void> task;
613
614                         if (completeRedraw)
615                                 task = getDrawPageTask(mPatchBm, patchViewSize.x, patchViewSize.y,
616                                                                 patchArea.left, patchArea.top,
617                                                                 patchArea.width(), patchArea.height());
618                         else
619                                 task = getUpdatePageTask(mPatchBm, patchViewSize.x, patchViewSize.y,
620                                                 patchArea.left, patchArea.top,
621                                                 patchArea.width(), patchArea.height());
622
623                         mDrawPatch = new CancellableAsyncTask<Void,Void>(task) {
624
625                                 public void onPostExecute(Void result) {
626                                         mPatchViewSize = patchViewSize;
627                                         mPatchArea     = patchArea;
628                                         mPatch.setImageBitmap(mPatchBm);
629                                         mPatch.invalidate();
630                                         //requestLayout();
631                                         // Calling requestLayout here doesn't lead to a later call to layout. No idea
632                                         // why, but apparently others have run into the problem.
633                                         mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom);
634                                 }
635                         };
636
637                         mDrawPatch.execute();
638                 }
639         }
640
641         public void update() {
642                 // Cancel pending render task
643                 if (mDrawEntire != null) {
644                         mDrawEntire.cancelAndWait();
645                         mDrawEntire = null;
646                 }
647
648                 if (mDrawPatch != null) {
649                         mDrawPatch.cancelAndWait();
650                         mDrawPatch = null;
651                 }
652
653
654                 // Render the page in the background
655                 mDrawEntire = new CancellableAsyncTask<Void, Void>(getUpdatePageTask(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y)) {
656
657                         public void onPostExecute(Void result) {
658                                 mEntire.setImageBitmap(mEntireBm);
659                                 mEntire.invalidate();
660                         }
661                 };
662
663                 mDrawEntire.execute();
664
665                 updateHq(true);
666         }
667
668         public void removeHq() {
669                         // Stop the drawing of the patch if still going
670                         if (mDrawPatch != null) {
671                                 mDrawPatch.cancelAndWait();
672                                 mDrawPatch = null;
673                         }
674
675                         // And get rid of it
676                         mPatchViewSize = null;
677                         mPatchArea = null;
678                         if (mPatch != null) {
679                                 mPatch.setImageBitmap(null);
680                                 mPatch.invalidate();
681                         }
682         }
683
684         public int getPage() {
685                 return mPageNumber;
686         }
687
688         @Override
689         public boolean isOpaque() {
690                 return true;
691         }
692 }