0

I am trying to render multiple PDF pages on a custom View in Xamarin.

    public override void Draw( AG.Canvas canvas ) {
        base.Draw( canvas );
        if( !(Parent is AW.ScrollView p) || pdfRenderer == null )
            return;
        int topPage = pdfRenderer.PageCount * p.ScrollY / Height;
        while( topPage < pdfRenderer.PageCount && topPage * screenPageHeight < p.ScrollY + p.Height ) {
            using( var page = pdfRenderer.OpenPage( topPage ) ) {
                page.Render( bitmap, null, null, PdfRenderMode.ForDisplay );
                page.Close();
            }
            AG.Rect pageRect = new AG.Rect {
                Left = 0,
                Top = topPage * screenPageHeight,
                Right = Width,
                Bottom = (topPage + 1) * screenPageHeight,
            };
            canvas.DrawBitmap( bitmap, null, pageRect, null );
            topPage++;
        }
    }

The output is drawing the page from the last loop run in all pageRect's. I can imagine why it might happen, but the real question is how I can fix this code to draw all pages without creating a separate bitmap for each page.

EDIT. Debugger screenshots attached. (At canvas.DrawBitmap breakpoint)

EDIT. Added java code. Expected: one red and one green rect. Observed: two green rects.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new TestView());
    }

    class TestView extends View {
        public TestView() {
            super(MainActivity.this);
        }

        Bitmap bmp = Bitmap.createBitmap(600, 200, Bitmap.Config.ARGB_8888);
        Paint paint = new Paint();

        void FillBitmap(int color) {
            Canvas canvas = new Canvas(bmp);
            canvas.drawColor(color);
        }

        protected void onDraw (Canvas canvas) {
            FillBitmap(Color.RED);
            canvas.drawBitmap(bmp, 60, 120, paint);
            FillBitmap(Color.GREEN);
            canvas.drawBitmap(bmp, 60, 440, paint);
        }

        protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(720, 720);
        }
    }
}

enter image description here enter image description here

Anton Duzenko
  • 2,366
  • 1
  • 21
  • 26
  • What am I supposed to see? – Anton Duzenko Jun 12 '18 at 18:58
  • Attached two screenshots – Anton Duzenko Jun 13 '18 at 07:39
  • It's Visua;Studio2017 – Anton Duzenko Jun 13 '18 at 08:12
  • A bmp can only be so large. Depending on the size of your PDF, this might be impossible without severe downscaling to the point of illegibility. Can I ask, what's your goal in having it not produce multiple bmp? Is this due to resource limitation? Or you just prefer to have a single file? Is it possible you can continue to generate multiple bmp and stitch them together into another format? or are you trying to prevent there being multiple bmp to begin with? – David Hollowell - MSFT Jun 14 '18 at 19:40
  • Yes, I want to keep the memory use as low as possible. – Anton Duzenko Jun 14 '18 at 20:00
  • I'm not sure this would reduce memory usage. If I have multiple smaller files I only need to store them in memory, for the converter, temporarily until they're written to disk. With one large image, you'd have to keep the whole thing in memory. Are you having troubles with memory usage/ cleanup in the existing scenario? Do you believe using the one large image would reduce memory usage during conversion? Can you explain? – David Hollowell - MSFT Jun 18 '18 at 19:01
  • It also seems like it would use more memory in the viewing scenario as well. Again, because we'd have to have one large image in memory, rather than smaller ones which we could free once it's no longer needed. Are you concerned with the size of the file headers? – David Hollowell - MSFT Jun 19 '18 at 16:44
  • Can we focus more on the question in hand and less on your memory management ideas? – Anton Duzenko Jun 21 '18 at 09:45
  • Hi, as stated, a bitmap can only be so large (2GB). So the exact question doesn't have a great answer. I want to make sure I'm helping to solve the problem you need addressed. There are various methods we may be able to take to reduce the memory profile, but I need to understand the problem and goals to provide a relevant solution. A solution that could work for any PDF to be able to convert to a single bitmap doesn't exist, or would result in such extreme scaling that it would be illegible. Are you open to other suggestions? – David Hollowell - MSFT Jun 21 '18 at 18:45
  • Thanks, but the question is about duplicating bitmap content, not about memory management., – Anton Duzenko Jun 22 '18 at 07:05
  • Thanks, Anton. Other than combining the images into one bitmap, what can we help with? I don't understand if the question is specifically how to create single bitmap, or if you're pointing out an error? Can you clarify what's wrong, or what's needed? – David Hollowell - MSFT Jun 22 '18 at 16:48
  • The error: canvas draws N copies of the bitmap from the last loop run (instead of drawing a different bitmap from each loop run) – Anton Duzenko Jun 24 '18 at 18:28
  • Can you please share: the steps to reproduce the problem, the expected behavior, and the actual behavior? Do you have a link for a sample you followed for this? or link to a minimal project sample you created? – David Hollowell - MSFT Jun 27 '18 at 20:00
  • There's a code snippet in the question body. Basically you'll need two image sources. Load them both to the same Bitmap and draw them side by side inside a single View draw(). – Anton Duzenko Jun 29 '18 at 15:16
  • I'm sorry, I need something that readily demonstrates the issue. In this sample, I do not see how bitmap is created. I'll need a minimal sample (or steps to create a sample) that readily reproduces the issue, steps to cause the issues to reproduce, a way to determine if I'm seeing the same behavior. Additionally, it will help to know what's expected, and what's actually occurring. – David Hollowell - MSFT Jul 02 '18 at 20:16
  • 1
    Added simple java code that reproduces the issue. – Anton Duzenko Jul 11 '18 at 13:53
  • If this reproduces in java, native development for Android, then the issue is not specific to Xamarin, but specific to Android development. I believe this problem is the same that I described in my comment to Jordan's answer. When you tell the canvas to draw things, you're saying, when you render, draw this thing (in this case, a bitmap). The canvas doesn't render immediately. So, when it does get around to rendering, it renders the current value of the bitmap. – David Hollowell - MSFT Jul 11 '18 at 14:48
  • Since this isn't unique to Xamarin, but instead Android development, I'll let someone here answer the Android specific details. – David Hollowell - MSFT Jul 11 '18 at 14:50

1 Answers1

0

David and I believe this is related to using the Canvas with Drawable objects loaded from the same resource. Bitmap is a drawable. Xamarin is a very thin layer of wrappers over the APIs, so this isn’t likely related to Xamarin, but more specific to Android. Notice this section of the documentation: Note: Each unique resource in your project can maintain only one state, no matter how many different objects you instantiate for it. For example, if you instantiate two Drawable objects from the same image resource and change a property (such as the alpha) for one object, then it also affects the other. When dealing with multiple instances of an image resource, instead of directly transforming the Drawable object you should perform a tween animation. - https://developer.android.com/guide/topics/graphics/drawables - https://developer.android.com/reference/android/graphics/Canvas So, the most likely cause is that you’re creating bitmap instances from the same resource (the pdf). I can’t see this in your code snippet to confirm, but it’s the most likely cause. It may also be related to using the same instance of bitmap repeatedly, rather than multiple separate bitmap instances, but I can’t see this from the snippet either. It may be less likely to be the cause than the previously mentioned issues with Drawables on the Canvas. Check to see if tween animation will help you in your scenario.

Note though, there may be more simplified methods to present the PDF. Android (and Xamarin.Andriod) have API for PdfRenderer. Android’s documentation for this is here: https://developer.android.com/reference/android/graphics/pdf/PdfRenderer Xamarin.Android and we have API docs for the Xamarin wrapper here: https://developer.xamarin.com/api/type/Android.Graphics.Pdf.PdfRenderer/ Alternatively, you can also set a pdf as the source for a WebView in Xamarin.Android or Xamarin.Forms, by setting the Source property to the pdf in Xamarin.Forms.WebView or by calling LoadUrl from an instance of Android.Webkit.WebView.

  • What do you mean by Canvas.Render? I don't see any reference of it on the page you linked to. DrawBitmap is called in each iteration. – Anton Duzenko Jun 27 '18 at 13:29
  • I don't have multiple bitmaps, only one. I create it in memory, not from resources. The code snippet shows how I render pdf into it. – Anton Duzenko Jul 07 '18 at 05:19
  • You'll likely need to use a list of bitmaps for this. When you tell the canvas to show the bitmap, it doesn't do so immediately. It's telling the canvas, next time you draw, draw this. When the canvas does finally render, it renders the current value of bitmap. I'll still need a sample to confirm; I can't see where some of your values are initialized or the context of how Draw is called. Can you share a project (or instructions for creating a project) that will readily reproduce the issue. Jordan tried to reproduce this and it did not repro for her, so we'll need the broader sample. – David Hollowell - MSFT Jul 10 '18 at 14:44
  • There's a guide here for creating minimal repro samples: https://stackoverflow.com/help/mcve – David Hollowell - MSFT Jul 10 '18 at 17:49
  • 1
    A java sample added. – Anton Duzenko Jul 11 '18 at 13:53