/* * Copyright (C) 2020 The Android Open Source Project * * 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. */ package com.android.bips; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.pdf.PdfDocument; import android.os.AsyncTask; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.print.PrintAttributes; import android.print.pdf.PrintedPdfDocument; import android.util.Log; import java.io.FileOutputStream; /** * A background task that optimizes a {@link Bitmap}, renders it into a PDF, and delivers the PDF * to a {@link ParcelFileDescriptor}. */ class ImageToPdfTask extends AsyncTask { private static final String TAG = ImageToPdfTask.class.getSimpleName(); private static final boolean DEBUG = false; private static final float POINTS_PER_INCH = 72; private final PrintedPdfDocument mDocument; private final Bitmap mBitmap; private final PrintAttributes mAttributes; private final int mDpi; private final CancellationSignal mCancellationSignal; ImageToPdfTask(Context context, Bitmap bitmap, PrintAttributes attributes, int dpi, CancellationSignal cancellationSignal) { mBitmap = bitmap; mAttributes = attributes; mCancellationSignal = cancellationSignal; mDpi = dpi; mDocument = new PrintedPdfDocument(context, mAttributes); } @Override protected Throwable doInBackground(ParcelFileDescriptor... outputs) { try (ParcelFileDescriptor output = outputs[0]) { if (DEBUG) Log.d(TAG, "creating document at dpi=" + mDpi); writeBitmapToDocument(); mCancellationSignal.throwIfCanceled(); if (DEBUG) Log.d(TAG, "writing to output stream"); mDocument.writeTo(new FileOutputStream(output.getFileDescriptor())); mDocument.close(); if (DEBUG) Log.d(TAG, "finished sending"); return null; } catch (Throwable t) { return t; } } /** Create a one-page PDF document containing the bitmap */ private void writeBitmapToDocument() { PdfDocument.Page page = mDocument.startPage(1); if (mAttributes.getMediaSize().isPortrait() == mBitmap.getWidth() < mBitmap.getHeight()) { writeBitmapToPage(page, true); } else { // If user selects the opposite orientation, fit instead of fill. writeBitmapToPage(page, false); } mDocument.finishPage(page); } private void writeBitmapToPage(PdfDocument.Page page, boolean fill) { RectF extent = new RectF(page.getInfo().getContentRect()); float scale; boolean rotate; if (fill) { // Fill the entire page with image data scale = Math.max(extent.height() / POINTS_PER_INCH * mDpi / mBitmap.getHeight(), extent.width() / POINTS_PER_INCH * mDpi / mBitmap.getWidth()); rotate = false; } else { // Scale and rotate the image to fit entirely on the page scale = Math.min(extent.height() / POINTS_PER_INCH * mDpi / mBitmap.getWidth(), extent.width() / POINTS_PER_INCH * mDpi / mBitmap.getHeight()); rotate = true; } if (scale >= 1) { // Image will need to be scaled up drawDirect(page, extent, fill, rotate); } else { // Scale image down to the size needed for printing drawOptimized(page, extent, scale, rotate); } } /** * Render the source bitmap directly into the PDF */ private void drawDirect(PdfDocument.Page page, RectF extent, boolean fill, boolean rotate) { float scale; if (fill) { scale = Math.max(extent.height() / mBitmap.getHeight(), extent.width() / mBitmap.getWidth()); } else { scale = Math.min(extent.height() / mBitmap.getWidth(), extent.width() / mBitmap.getHeight()); } float offsetX = (extent.width() - mBitmap.getWidth() * scale) / 2; float offsetY = (extent.height() - mBitmap.getHeight() * scale) / 2; Matrix matrix = new Matrix(); if (rotate) { matrix.postRotate(90, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2); } matrix.postScale(scale, scale); matrix.postTranslate(offsetX, offsetY); page.getCanvas().clipRect(extent); page.getCanvas().drawBitmap(mBitmap, matrix, new Paint(Paint.FILTER_BITMAP_FLAG)); } /** * Scale down the bitmap to specific DPI to reduce delivered PDF size */ private void drawOptimized(PdfDocument.Page page, RectF extent, float scale, boolean rotate) { float targetWidth = (extent.width() / POINTS_PER_INCH * mDpi); float targetHeight = (extent.height() / POINTS_PER_INCH * mDpi); float offsetX = ((targetWidth / scale) - mBitmap.getWidth()) / 2; float offsetY = ((targetHeight / scale) - mBitmap.getHeight()) / 2; Bitmap targetBitmap = Bitmap.createBitmap((int) targetWidth, (int) targetHeight, Bitmap.Config.ARGB_8888); Canvas bitmapCanvas = new Canvas(targetBitmap); Matrix matrix = new Matrix(); matrix.postScale(scale, scale); if (rotate) { matrix.postRotate(90, targetWidth / 2, targetHeight / 2); } bitmapCanvas.setMatrix(matrix); bitmapCanvas.drawBitmap(mBitmap, offsetX, offsetY, new Paint(Paint.FILTER_BITMAP_FLAG)); page.getCanvas().drawBitmap(targetBitmap, null, extent, null); targetBitmap.recycle(); } }