blob: 65e523c2de5b8fe9583671b55247d97d73545379 [file] [log] [blame]
/*
* 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<ParcelFileDescriptor, Void, Throwable> {
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();
}
}