Strange scaling in canvas using high density screen (Android)

877 views
Skip to first unread message

Luis Medel

unread,
Sep 17, 2010, 4:32:09 AM9/17/10
to phonegap
Hi all,

First of all, excuse me if this has been asked before. I've been
diving in the archives and, as far as I know, nobody has experienced
this issue :)

I'm facing some problems while developing an app which makes heavy use
of html5 canvas under Android.

I have to say that I have no problems at all positioning all my UI
elements using javascript (I need pixel-perfect positioning regardless
of screen dimensions). I'm experiencing a strange behavior only when
drawing in a canvas using high density screens.

If I make use of drawing primitives (moveTo, lineTo, fillRect...) I
have no problems and everything looks as I want, but, when I use the
drawImage function, the image is painted scaled up by a factor of 1.5
(position and size). The canvas object seems to manage pixels values
in a different manner when using drawImage.

I've read a lot of docs about screen densities, but can't find a
suitable solution for this. I'm thinking in applying a scaling factor
to all drawImage calls, but it will be a mess in my code :(

Curiously, testing the app in the iPhone 4 simulator, it works as
expected, so I think this may be an Android bug.

Any ideas?
Thanks

D. Rimron

unread,
Sep 17, 2010, 10:49:28 AM9/17/10
to phon...@googlegroups.com
On 17/09/2010 09:32, Luis Medel wrote:
> If I make use of drawing primitives (moveTo, lineTo, fillRect...) I
> have no problems and everything looks as I want, but, when I use the
> drawImage function, the image is painted scaled up by a factor of 1.5
> (position and size). The canvas object seems to manage pixels values
> in a different manner when using drawImage.

I'm guessing here..... CSS uses Android's Virtual Pixels, Canvas uses
Physical?

Have you set a meta viewport scale/width in your HTML, is that what is
engaging the virtual pixel scale for CSS?

As I said, pure guesses that maybe will help. I don't use canvas. (read:
I hate canvas :) )

-Dx

Luis Medel

unread,
Sep 17, 2010, 1:49:35 PM9/17/10
to phonegap
Hi,

Thanks for your reply.

I think It's not a case os "real pixels vs CSS pixels" because
moveTo(10,10) always (under all screen densities) moves the graphic
cursor to the position 10,10 but drawImage(my_image, 10, 10) puts the
image at 10,10 in a mdpi screen and at 15,15 in a hdpi screen.

This behaviour occurs only in Android. In iPhone, moveTo and drawImage
works in a homogeneous way.

Also, I'm using this META tag to avoid any automatic scaling:

<meta name="viewport" content="width=device-width, target-
densityDpi=device-dpi, initial-scale=1.0, user-scalable=no, maximum-
scale=1.0">


Greetings.

Luis Medel

unread,
Sep 18, 2010, 1:31:09 PM9/18/10
to phonegap

> moveTo(10,10) always (under all screen densities) moves the graphic
> cursor to the position 10,10 but drawImage(my_image, 10, 10) puts the
> image at 10,10 in a mdpi screen and at 15,15 in a hdpi screen.


Finally I solved this problem easily. I override
CanvasRenderingContext2D.prototype.drawImage when running with a hdpi
screen.
I hope this can be useful for somebody else :)

if (SCREEN_DENSITY !== DENSITY_MEDIUM)
{
var factor = DENSITY_MEDIUM / SCREEN_DENSITY,
drawImage = CanvasRenderingContext2D.prototype.drawImage;

CanvasRenderingContext2D.prototype.drawImage = function (image,
sx, sy, sw, sh, dx, dy, dw, dh) {
for (var i = arguments.length - 1; i >= 1; i--)
arguments[i] = arguments[i] * factor;

drawImage.apply (this, arguments);
};
}


--
Luis Medel

Björn Nilsson

unread,
Sep 18, 2010, 6:07:49 PM9/18/10
to phonegap
Hi Luis,

What you have seen and found a workaround for is probably this bug:
http://code.google.com/p/android/issues/detail?id=5141

Thanks for sharing this, I will try it. My current workaround for this
is to fall back to not use canvas at all...

Björn Nilsson

unread,
Sep 22, 2010, 5:03:37 PM9/22/10
to phonegap, lu...@luismedel.com
Hi Luis,

How do you define the values of the constants SCREEN_DENSITY and
DENSITY_MEDIUM? Are they predefined bt PhoneGap API?

Do you always apply this code, regardless of android version, or do
you do user agent detection?

How is the performance of this on a real device, when you do
animation?

Luis Medel

unread,
Sep 22, 2010, 8:36:12 PM9/22/10
to phonegap
Hi Björn,

>
> How do you define the values of the constants SCREEN_DENSITY and
> DENSITY_MEDIUM? Are they predefined bt PhoneGap API?
>

I've modified Phonegap to pass some system info as URL parameters.
Then, from javascript, I read and store them as constants.
Also, I've defined the screen density constants (DENSITY_LOW,
DENSITY_MEDIUM and DENSITY_HIGH) as 120, 160 and 240 respectively, so
I can make some math against the screen density.

I override drawImage only under Android 2.0 and 2.1. I think these are
the only Android versions wich come with this nasty bug. Maybe I write
some code to test the bug on the fly, but for now it works as I want.

>
> Do you always apply this code, regardless of android version, or do
> you do user agent detection?
>

I can't tell you how much this will affect performance on a real
device, as my test device is a HTC Magic with a medium density display
and a "slow" 500Mhz CPU. In the emulator, the code seems to work
reasonably well. At least on my crappy laptop I can't tell the
difference between native and modified drawImage (and my app draws on
the canvas intensively)

>
> Do you always apply this code, regardless of android version, or do
> you do user agent detection?
>

I think this must perform well, as devices with high density displays
all come with fast CPUs (more than 500Mhz if I'm not wrong), but I
have to test in a friend's Nexus One to be sure.
Anyone interested in testing my code in a real phone with a high
density display, please? :)

--
Luis Medel

Luis Medel

unread,
Sep 22, 2010, 8:41:40 PM9/22/10
to phonegap
Doh! Excuse me.
I messed up the questions, but I think you can understand the
answers :)

--
Luis Medel

Björn Nilsson

unread,
Sep 23, 2010, 4:10:04 AM9/23/10
to phonegap
Thanks Luis, it is very helpful.

I have this issue right now with my app and since most folks seem to
be on Android 2.1 its important to solve. I will report what I find.

Currently I have some user agent detection code to detect Android 2.1
aswell, but it doesnt seem to kick in if the user have the HTC
modified Android with Sense UI. Sigh. What is your code to detect
Android 2.1 and 2.0?

Mine is:
function isUA(name) {
if (navigator.userAgent.indexOf(name) != -1) {
return true;
}

return false;
}

if (isUA("Android 2.1")) {
...do workarounds...
}

But it doesnt seem to work in all cases.

Luis Medel

unread,
Sep 23, 2010, 1:09:44 PM9/23/10
to phonegap
Hi,

>
> Currently I have some user agent detection code to detect Android 2.1
> aswell, but it doesnt seem to kick in if the user have the HTC
> modified Android with Sense UI. Sigh. What is your code to detect
> Android 2.1 and 2.0?
>

You can try to use the URL param trick to pass some values to your
app.
In this example I pass the screen dimensions, screen density and
Android version.

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

Display display = ((WindowManager)
getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

DisplayMetrics metrics = new DisplayMetrics ();
display.getMetrics (metrics);

String version = android.os.Build.VERSION.RELEASE;
int width = metrics.widthPixels;
int height = metrics.heightPixels;
int density = metrics.densityDpi;

metrics = null;
display = null;

String url = String.format ("file:///android_asset/www/index.html?w=
%d&h=%d&d=%d&v=%s", width, height, density, version);
super.loadUrl(url);
}


Then, you can read those params with this function:

function getParam (name, def)
{
var url = document.location.toString();
if (url.indexOf ("?") === -1)
return def;

var r = new RegExp ("[\\?\\&]" + name + "\\=([^\\&]+)"),
m = r.exec (url);

if (m === null)
return def;

return m[1];
}


OS_VERSION = getParam ("v");
SCREEN_WIDTH = getParam ("w");
SCREEN_HEIGHT = getParam ("h");
SCREEN_DENSITY = getParam ("d");

I hope you find this useful.
Greetings.

Björn Nilsson

unread,
Sep 23, 2010, 5:45:08 PM9/23/10
to phonegap
Thanks,

I tried it and it works great for detection.

I have experimented with the overriding of drawImage with a Scaling
factor like you have done. Even though it works, I have found that it
gives scaling artefacts and blurriness. Performance is very slow,
probably due to images being scaled anyway by the broken Android 2.1
implementation. So I have choosen to not use canvas at all if this bug
is present.

I also implemented the function below that can detect if this bug is
present, using only JavaScript and no PhoneGap modifications. Has the
benefit that it can also be used if the app is running outside of
PhoneGap directly in the Android browser. It draws an image on an off
screen canvas, and then checks if it ended up in the wrong place.

function hasDrawImageScalingBug() {
var iw = 1;
var ih = 1;

var img = document.createElement("canvas");
img.width = iw;
img.height = ih;

var ictx = img.getContext("2d");
ictx.fillStyle = "#ffffff";
ictx.fillRect(0, 0, iw, ih);

var bw = 100;
var bh = 100;

var buffer = document.createElement("canvas");
buffer.width = bw;
buffer.height = bh;

var bctx = buffer.getContext("2d");
bctx.clearRect(0, 0, bw, bh);
bctx.drawImage(img, bw/2, bh/2);

var imageData = bctx.getImageData(0, 0, bw, bh);
var sample = imageData.data[bw/2*bw*4+bh/2*4];

if (sample == 0) {
return true;
}

return false;
Reply all
Reply to author
Forward
0 new messages