Canvas drawing and Retina display: doable? - iphone

Working with phoneGap implementing drawing with Canvas. The catch we've run into is that canvas expects specific pixel dimensions. This is fine except that the iPhone 4's Retina display, from a CSS/Webkit POV is still 320px wide, even though technically there are 640 actual screen pixels.
Is there anyway to accommodate the retina display using Canvas on Webkit while preserving compatibility with non-retina displays?

I sat with the same problem last week and discovered how to solve it -
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
if (window.devicePixelRatio > 1) {
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
canvas.width = canvasWidth * window.devicePixelRatio;
canvas.height = canvasHeight * window.devicePixelRatio;
canvas.style.width = canvasWidth + "px";
canvas.style.height = canvasHeight + "px";
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
}
Full code on gist, demo on jsfiddle

There is a drop-in polyfill that will take care of most basic drawing operations for you, and remove the ambiguity between browsers that handle this automatically for you (safari) and others that don't.
https://github.com/jondavidjohn/hidpi-canvas-polyfill
You simply include it before your drawing code and it should give you decent retina support automatically.

I couldn't find anywhere else on the internet suggesting this so I figured it out. If you have a full screen canvas and you want it to be the actual amount of pixels that the device has, just remove this line from your HTML:
<meta name='viewport' content='width=device-width' />
Don't set the viewport at all. Then you just do:
canvas.width = innerWidth
canvas.height = innerHeight
canvas.style.width = innerWidth+'px'
canvas.style.height = innerHeight+'px'
That will use the full screen resolution of the device.
A pixel will always be a pixel.
You don't need to scale.
And getImageData() will give the same values that you see.

WebCode (http://www.webcodeapp.com) is a vector drawing app that generates JavaScript HTML5 Canvas code for you. The code is Retina-compatible, you can check out how they do it.

There is a very good polyfill by TJ Holowaychuk:
https://www.npmjs.com/package/autoscale-canvas

EDIT: Just noticed I posted the wrong link for the demo!
Retina (or other hdpi display) canvas resolution is definitely possible. There is a working example here:
http://spencer-evans.com/share/github/canvas-resizer/
I've also bumped into this a few times. The accepted answer code is essentially correct, but you could also use a library solution. I whipped one up to handle intelligent canvas re-sizing to make the element more responsive and higher resolution (as shown in the demo).
https://github.com/swevans/canvas-resizer

Related

How to scale fixed size HTML5 webapp games?

I am developing a fixed size HTML5 DOM-based game. It got animations and graphics naturally. It is fixed at 1000px x 620px but I cant make the game scale down proportionally for different sizes of devices. I fits nicely on iPad but I only see part of the game on iPhone for example.
I thought it was easy thing to do to just scale the whole thing down (inc. graphics) but I can't find a solution for that.
What are my options, right now I am thinking of 3 totally diffenct CSS with media queries one for iPad, iPad mini and iPhone. but its seem to be a lot of extra work to do.
Thanks for any help!
You could try iframing your page and using standard transforms to scale it to the size you need. It is probably better to make your game non height/width dependent, but this may work to fix the issue quickly:
iframe {
-ms-zoom: 0.25;
-transform: scale(0.25);
-moz-transform: scale(0.25);
-moz-transform-origin: 0 0;
-o-transform: scale(0.25);
-o-transform-origin: 0 0;
-webkit-transform: scale(0.25);
-webkit-transform-origin: 0 0;
}

Images disappear during translate3d on Chrome for iPhone and iPad

I have a coverflow type image gallery that I have created for mobile picture viewing. The first iteration lives here: http://codepen.io/jasonmerino/pen/Fsloq.
I've wired up the touch events and all seems to be going well for mobile Safari on iPhone and iPad, but when I go to view it on Chrome for iPhone or iPad the images disappear during part of the CSS translate3d which moves the images to the sides.
I have added -webkit-backface-visibility: hidden; to all of the markup that comprises the swiper, which does not fix this disappearing act my images are doing.
What am I missing here? Any help would be appreciated. Thanks!
The problem was with the peice of code where I was removing inlined CSS translate3d. The code that I had in place dealt with removing the translate by just re-assigning the background images.
for (var i = 0; i < images.length; i++) {
$el.images.eq(i).css('style', 'background-image: url(' + images[i].src + ')');
}
In mobile Safari this was fine, but in Chrome the images reloaded when I re-assigned the background image, hence the disappearing act. So I adjusted my code to be a little smarter, and concise, so that I only removed the translate from the inlined styles.
$el.images.css('transform', '');
Turns out that when you pass an empty string as the value to a CSS attribute in jQuery they just remove the style out for you instead of leaving it there but blank. Makes sense, but I never knew that before.
Anyway, that's what fixed it for me.

App Crashing when using large images on iOS 4.0

I've been having a problem to show large images on a scrollview, the images are 2,4 - 4,7 MB. It runs fine on the 3GS and on the simulator. But whenever I try to run on a 3G or iPod Touch 2G, it crashes.
I searched across the web and found the "imageNamed is evil" article. Ok I changed all my image calls to imageWithContentsOfFile: but it still crashes, the only different that I see is that now images are deallocated after I leave the view just fine. But the memory usage is still very high.
Here is a screenshot of Instruments.
First peak is a video I show at startup, then the tableview shows a lot of images, until then no problems.
When I enter a 1,1mb - 2576 x 1000 picture
When I enter a 4,8mb - 7822 x 1000 picture
By the way the app was tested on iOS 4 and 3.1.2
Do you have any tips? Because this problem is driving me nuts.
Since now thanks a lot!
I recently ran into the same problem while testing an app on my 3G. I ended up scaling down any images larger than a maximum number of pixels (I found that 2 million pixels seemed to work reliably on my 3G, but hotpaw2's answer seems to suggest that 1 million pixels may be a safer bet).
UIImage *image = // ...;
if (image.size.width * image.size.height > MAX_PIXELS) {
// calculate the scaling factor that will reduce the image size to MAX_PIXELS
float actualHeight = image.size.height;
float actualWidth = image.size.width;
float scale = sqrt(image.size.width * image.size.height / MAX_PIXELS);
// resize the image
CGRect rect = CGRectMake(0.0, 0.0, floorf(actualWidth / scale), floorf(actualHeight / scale));
UIGraphicsBeginImageContext(rect.size);
[image drawInRect:rect];
UIImage *imageToDraw = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// imageToDraw is a scaled version of image that preserves the aspect ratio
}
Apple also provides an example of developing a photo gallery app that uses CATiledLayer to tile very large images. Their example uses images that have been sliced into tiles of the appropriate sizes in advance. It is possible to slice the images into tiles on the fly in your iOS app, but doing so is quite slow on the device. Check out session 104 of this year's WWDC for the PhotoScroller example.
The GPUs in the iPhone 3G and iPod Touch 1G/2G only have room for 1k by 1k textures, which seems to also be used for 2D image rendering. Anything larger needs to be tiled to fit the hardware graphics renderer.
The 3GS and newer have a different GPU which can support larger textures, and thus images.
I ended up using UIWebView, I had to use smaller (in MB, not dimensions) images but it's working on, the only problem Is that I can't zoom... Using the scalePageToFit I can zoom but the image fits horizontally not vertically, that sux.
I will try to keep this issue updated.
If someone has this problem I would advice to use webview or an adapter version of three20 image viewer that handles memory better.

Blurred text in Iphone 4 browser when loading content dynamically

I am using the Jquery/Jqtouch libraries for an iphone compatible site. I am now stuck with a problem just in iPhone 4 (not in 2g, 3g or 3gs) where the text becomes blurry on one specific scenario. Below is how it happens
The site has one common div container.
<div id="container"></div>
The container is filled with content dynamically based on the user action. Below is the function that does that.
function loadPage(url, section, callback) {
$('#container').empty();
$('#container').load(url + ' ' + section, loadComplete(section));
}
One sample call to the above function
loadPage("Data.htm", "#Friends", null);
Basicaly eveything works fine except on one scenario where the amount the data on the container is huge (ie the #container height increases to 1500px+ not predictable). Now if i replace it with smaller data for different tabs on the same container then the text becomes blurry. Attached is the image
http://i.stack.imgur.com/XE9q4.png
Did anyone come across this scenario. Thanks in advance.
Try closing all your running apps besides safari. It sounds crazy but we have the same problem on the ipad and it just seems to be running out of memory at some point. Closing all the apps stops it. Other thing that seems to make a difference is -webkit-overflow-scrolling:touch, if it doesnt have this property then it doesnt seem to have the problem described.
i was able to fix this by applying the same settings to reduce flicker on the element in webkit browsers:
-webkit-perspective: 1000;
-webkit-transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
Graphics elements must be "aligned" with the pixels on the screen; coordinates must always expressed as integral values and not floating values. If not, the subpixel rendering engine of the GPU would make it blurry, which is not a problem with animation but definitely one with static images.
In the native SDK, we have to make sure everything is aligned (such as using CGRectMakeIntegral()).
Since you're using a web framework, it's more difficult to tell how to exactly how fix the problem, but I would try to adjust the sizes of your to a precise size and not let the framework figure it out.
What content do you load? Images? Text? There's an internal limit on image sizes for the iPhone (about 4 Megapixels or so). It looks like the phone is trying to reduce the memory load of your website and reduces the resolution to non-retina values.
I can't say more without you posting code.
This is a shot in the dark, but have you aset your sizes using pt values for your block elements, and em for your text?
The iphone4 resizes your content to fit its higher-res Retina display (compared to the older iphone), and with that scaling i have sometimes noticed blur when using pixel values for block height, width, font size, etc.
Very hard to diagnose without seeing the actual code, but could be the issue.
In my case it was CSS
-webkit-transform: translateZ(0);
applied to one of the elements in body. So as Ariejan said, it's removing transition property that fixes it.
body{ text-rendering: optimizeLegibility}
could solve this issue, worth a shot if you haven't included it already
Sometimes, Text blurry may be cause of the iScroll Plugin. Did you use this?
Try to comment
trnOpen = 'translate' + (has3d ? '3d(' : '('),
trnClose = has3d ? ',0)' : ')',

Safari iPhone - How to detect zoom level and offset?

I looking for options on how to track user zooming and panning on a page when viewed in Safari on an iPhone. Safari exposes move and gesture events, so theoretically I can keep a running tally of pan and zoom operations, but that seems like overkill since the browser must track that internally.
Is this information exposed through the Document Object Model?
When you zoom in, window.innerWidth is adjusted, but document.documentElement.clientWidth is not, therefore:
var zoom = document.documentElement.clientWidth / window.innerWidth;
(I've tested iOS4, without viewport <meta>).
However, I wouldn't rely on it for anything important. DOM viewport sizes/pixel sizes in mobile browsers are a complete mess.
On Mobile Safari and Android, here is an accurate way to measure how much the page has been zoomed.
Try it here: http://jsbin.com/cobucu/3 - change zoom then click measure.
Technique is to add a top level div:
<body>
<div id=measurer style="position:absolute;width:100%"></div>
and use the calculation:
function getZoom(){
return document.getElementById('measurer').offsetWidth / window.innerWidth;
}
The only problem is finding a tidy way to detect that the user has changed zoom (pinch, double tap, etc). Options:
webkitRequestAnimationFrame: very reliable, but likely to cause jankiness if using animations (due to performance hit)
setInterval: reliable but very ugly
touch events: look for two fingers or
double tap: ugly and maybe difficult to make 100% reliable
window.onresize + window.onorientationchange + window.onscroll: simple but totally unreliable (Edit: and onscroll can cause performance problems in WKWebView or Mobile Safari 8 or greater).
PS: Windows Phone needs a different solution (pinch-zoom doesn't change the viewport - pinch-zoom on Windows has its own separate viewport that is not visible to javascript).
Edit: Android Visual Viewport resize and scroll events may help? See https://developer.mozilla.org/en-US/docs/Web/API/VisualViewport#Events
According to the Safari Web Content Guide, zoom events (double tap) are not exposed, so I'm not sure how you can track this.
I do not believe this information is exposed through the DOM.
I actually think things might have moved on a little since Steve's answer, as having a look at the content guide link he provided I can see a section on Handling Multi-Touch Events and also Handling Gesture Events.
Haven't tried them yet but they look pretty promising. I'll provide an update once I've checked them out and have a demo link available...
I measure zoom this way (works on iOS only):
screenOrientedWidth = screen.width;
if (window.orientation == 90) {
screenOrientedWidth = screen.height;
}
return screenOrientedWidth / window.innerWidth;
It doesn't depend of how wide content is.
However, in iOS Safari window.innerWidth isn't correct inside a gestureend handler. You should defer such calculation for later execution. In GWT, I use scheduleDeferred, but I can't say how to implement this in pure JavaScript.
If you are using any elements with location:fixed this can get complicated, as the location:fixed coordinates are relative to the unzoomed window, where window coordinates are relative to the zoomed viewport. More info: How to position a fixed-location element on IOS browser when zoomed?