Problems Scrolling and Zooming an Image in an WP8.1 App - windows-runtime

I'm struggling with the ScrollViewer in my Windows Phone 8.1 (WinRT) app. Basically, what I'm trying to achieve is to retrieve an image using FileOpenPicker, crop the image to a fixed ratio (square) format while letting the user select the part of the image and the zoom level, and then use that image in my app. Perfect would be functionality as in the "People" app where you can add an image to a contact, but I would settle for less if I could somehow get it to work without the ScrollView acting too erratically.
Here is one of the variations I tried:
<ScrollViewer x:Name="SelectedImageScrollViewer"
ZoomMode="Enabled"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
Height="300"
Width="300" >
<Image x:Name="SelectedImage"
Source="{Binding SelectedImage}"
MinHeight="300"
MinWidth="300" />
</ScrollViewer>
and in code-behind (in the constructor):
if (SelectedImage.ActualHeight > SelectedImage.ActualWidth) {
SelectedImage.Width = SelectedImageScrollViewer.ViewportWidth;
}
else {
SelectedImage.Height = SelectedImageScrollViewer.ViewportHeight;
}
Like I said, this isn't really working, and there are several problems with it:
ScrollViews have this kind of "rubber band" overscroll functionality built in. While I can agree on platform uniformity, here it isn't helpful, and the mentioned "People" app doesn't have that either.
When the user zooms beyond the MaxZoomLevel, zooming doesn't just stop, but the image drifts away and snaps back after releasing - not a good user experience.
The image can be made smaller than the cropping frame. It should not be possible to reduce the zoom level to the point where the image is not filling the viewport.
The ScrollView does not show the center of the image.
How can I fix those issues, and what would be the best approach for cropping and scaling the image? Would be nice if this was available as part of the SDK as it was in Silverlight (photo chooser).

The following solution provides a reasonably good user experience. Regarding the list of problems:
Apparently cannot be solved using the basic ScrollViewer.
Increasing MaxZoomFactor to something large enough makes it unlikely that the user sees the issue.
After setting the image's smaller dimension to the cropping frame size, a MinZoomFactor of 1 ensures that the image always fills the frame.
The ScrollView's offsets can be set in code behind.
Setting IsScrollInertiaEnabled and IsZoomInertiaEnabled to false removes some of the erratic behavior in scrolling an zooming. Image width and height are set in SelectedImage_SizeChanged because the initial actual dimensions are not available in the constructor (before the page is rendered).
<ScrollViewer Grid.Row="1"
x:Name="SelectedImageScrollViewer"
ZoomMode="Enabled"
IsScrollInertiaEnabled="False"
IsZoomInertiaEnabled="False"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
Height="300"
Width="300"
MinZoomFactor="1.0"
MaxZoomFactor="10.0">
<Image x:Name="SelectedImage"
Source="{Binding SelectedImage}"
HorizontalAlignment="Center"
SizeChanged="SelectedImage_SizeChanged" />
</ScrollViewer>
and
private void SelectedImage_SizeChanged(object sender, SizeChangedEventArgs e) {
// Here the proportions of the image are known and the initial size can be set
// to fill the cropping frame depending on the orientation of the image.
if (!_imageProportionsSet) {
if (SelectedImage.ActualWidth != 0) {
double actualHeight = SelectedImage.ActualHeight;
double actualWidth = SelectedImage.ActualWidth;
double viewPortWidth = SelectedImageScrollViewer.ViewportWidth;
double viewPortHeight = SelectedImageScrollViewer.ViewportHeight;
if (actualHeight > actualWidth) {
SelectedImage.Width = viewPortWidth;
double yOffset = (actualHeight - actualWidth) * viewPortWidth / actualHeight;
SelectedImageScrollViewer.ChangeView(0, yOffset, 1);
}
else {
SelectedImage.Height = viewPortHeight;
double xOffset = (actualWidth - actualHeight) * viewPortHeight / actualWidth;
SelectedImageScrollViewer.ChangeView(xOffset, 0, 1);
}
// Do this only once.
_imageProportionsSet = true;
}
}
}
This is workable. If you find any issues with this please don't hesitate to comment or to provide an improved answer.

Related

How can I make it so Edit2D lines do not resize when viewer is zoomed in and out?

Long story short: I want to set Edit2D polyline tool's line width to 6" based on calibration sizing and have it stay that size in the viewer no matter the camera zoom.
I'm using the edit2d library to allow drawing. I need to be able to set the line width for the polyline tool and have it stay at that width similar to how markups stay a set size when drawn. The default functionality of the edit2d polyline tool is for the line to be resized on camera changes and so it grows and shrinks depending on zoom.
I tried setting edit2DTools.polylineTool.style.isScreenSpace = false; which works, however, trying to set the specific size is difficult as it ends up being a decimal less then 1 and I can't find a correlation from the calibration, page size, etc. to allow me to dynamically set the size the same on different models.
I also found this in the Edit2D Snapper's code, but I can't figure out what's happening here to replicate it on the polyline tool. It seems to be doing what I want to do.
Any help or ideas at all would be greatly appreciated!
This code seems to be working for me:
async function setLineWidth(viewer) {
let mt = await viewer.loadExtension("Autodesk.Measure");
if (!mt.sharedMeasureConfig.calibrationFactor) {
console.log("Has not been calibrated yet");
return;
}
let edit2d = viewer.getExtension('Autodesk.Edit2D');
let pt1 = {x:0, y:0, z:0}, pt2 = {x:6, y:0, z:0};
viewer.model.pageToModel(pt1, pt2, 0 /*viewport id*/, true /*reverse*/);
let style = edit2d.defaultTools.polylineTool.style;
style.lineWidth = (pt2.x - pt1.x) / mt.calibration.scaleFactor;
}
Here is a video showing it in action: https://youtu.be/DIaKugvQdm4
As you can see first the lineWidth of the polylineTool is not what you want, but after setting what 6" should be in the drawing, I can set it to the same width and all is fine.
Here is a zip file with the html including the JavaScript code and the test PDFs I used: https://github.com/adamenagy/Container/blob/master/Edit2dTest.zip

How to keep fluid grid of image thumbnails from shifting during load?

I'm using a bootstrap 3 fluid grid to display thumbnails, and I love how the images scale in size as the browser is resized. The downside however, is a "big bang" effect when each page is loaded. That is, the grid begins collapsed then grows as images are added. I imagine a simple fix is to hardcode image sizes, but this would lose the scaling benefit I believe.
One attempt to fix this was to load a transparent placeholder image right before each thumbnail, which would of course be cashed on the first page of results and thus expand the grid faster. On callback for thumbnail loaded event, I remove the placeholder. This seems to help, but other times I still see the shifting as badly as before. In addition, with a slow connection you can actually for a moment see the real thumb below the placeholder.
<script type="text/javascript">
$(function(){
// For each thumbnail, insert a placeholder image.
// Once the thumb is loaded, remove the placeholder.
$("[id^=thumb-]").each(function(i, thumb) {
var $thumb = $(thumb)
var imgTag = "<img id='ph-" + (i + 1) +
"' class='placeholder' src='{% static "img/placeholder.png" %}'/>";
$thumb.parent().prepend(imgTag);
var $holder = $thumb.prev();
function loaded() {
$holder.remove();
}
if (thumb.complete) {
loaded();
} else {
$thumb.on('load', loaded);
$thumb.on('error', function() {
console.log('Error with thumbnail placeholders.');
});
}
});
});
</script>
Regarding compatibility, I'd like to at least have a usable site with older browsers, but it doesn't have to be perfect.
I'm not as interested in fixing my Javascript solution above as I am the best solution overall.
Please look at the live beta site here to help diagnose. I attempted a jsfiddle, but couldn't quite reproduce it. I will paste more context into the question once we understand what was wrong.
In this case, I would recommend adding the <img> tag to the plain HTML. Then set the src in your javascript function.
You'll also need to set height and width attributes on the <img> tags so their space is preserved, to prevent redrawing the page after the images are loaded. You could do this with a simple javascript function that determines the window.width and then sets the height and width attributes.
Something like this.

How to have an application to automatically scale when launched?

I want my application to automatically scale its size depending on the screen size. I want my application to fit any screen size automatically. I am making my application for mobile devices, how can I do this? I am fairly new at flash so please make it a bit simple!
Yes, there is no simple answer but the basics is:
stage.addEventListener(Event.RESIZE,onStageResize);
function onStageResize(e:Event):void {
// Use stage size for placing elements to their position..
// Position to left top
element1.x = 10;
element1.y = 10;
// Position to right top
element2.x = stage.stageWidth - element2.width - 10;
element2.y = 10;
// Position element to center and make it's width to scale with stage..
element3.width = stage.stageWidth - 20;
element3.x = 10;
element3.y = stage.stageHeight / 2 - element3.height / 2;
}
To scale elements place them inside on Sprite or MovieClip and scale that element like:
element.scaleX = 1.6; // scale 1 = 100% 2 = 200% etc
element.scaleY = element.scaleX;
I usually create public function "resizeElements(e:Event = null):void" for visual subclasses/components and call that from the main thread. In that function the object can resize it self.
In addition to #hardy's answer it's important to set the following variables on the stage:
// disables default 100% scaling of entire swf
stage.scaleMode = StageScaleMode.NO_SCALE;
// disables default align center
stage.align = StageAlign.TOP_LEFT;
Also, since the original question was regarding resize on launch, and this is mobile deployment it's important to call onStageResize(); once manually because the RESIZE event may not be fired initially. This would require changing:
function onStageResize(e:Event):void { ... }
to
function onStageResize(e:Event = null):void { ... }
Saying "automatically scale to screen" is too generic. The requirements this statement imposes is totally different between apps depending on what they need to do. There is no simple answer.
If your app has a main content canvas area, do you want to zoom to the content to fit the area? Do you want to keep the content the same size and show more of it? Maybe both - you could want to show more to a point, and then scale if there is still more room.
What do you want your UI to do? Dialogs could shrink/grow and remain central. Maybe you want to reflow them completely?
Do you have a slick UI? Maybe a menu with a set of buttons. Do the buttons need to be sized proportionally to the screen? What about the gaps between them? What if the screen ratio is different so if you scale them to fit they no longer work?
The point is, there isn't a simple answer. For basic layout there are UI frameworks that can assist you in placement and scaling, but you still need to decide how you want your app to behave. That totally depends on the app and what your intended design is.

CSS Aspect Ratio on Canvas

Recently, Mozilla launched a HTML5 game called Browser Quest. In the game, if you resized the window, the canvas would also resize.
I looked more into and I saw that it was beacuse of usign CSS3 Media Queries found here https://developer.mozilla.org/en/CSS/Media_queries
However, I still don't think I am doing it right. My canvas ID is #canvas. How would I go about putting it for my canvas?
my canvas specific width/height: height:352px; width:512px;
So you don't want to define size of a canvas in CSS since you will only ever be scaling it away from its "true" size. You always want to use the width and height attributes of the Canvas instead.
But that doesn't mean you can't define it's parent's size that way. Wrap the canvas in a div and set the div's CSS width/height to 100% (or whatever you please)
In code during setup you are going to have to do:
// javascript pseudocode
canvas.width = theCanvasParent.clientWidth; // or whatever attribute it is, I'd reccomend putting all of those things in one giant container div
canvas.height = theCanvasParent.clientHeight;
Since most browsers do not fire an event when the parent div changes size, you'll simply have to check, say, every half second with a timer to see if the div has changed size. If it has, then you resize the canvas accordingly.
However there is the onresize event, and depending on how your page is setup this may do the trick.
In Firefox, Opera, Google Chrome and Safari, the onresize event is fired only when the size of the browser window is changed.
In Internet Explorer, the onresize event is fired when the size of the browser window or an element is changed.
So if the only way to change your div's size is by changing the window's size, onresize will do you just fine. Otherwise you'll need a timer that constantly checks to see if the canvas size and div size are different (and if so, to resize the canvas).
A timer that constantly checks is what the Mozilla Bepsin team did (before Bespin became Skywriter and then merged with the Ace project, dropping all Canvas use)
Media queries won't provide you with the functionality you seek. Their purpose is simply to limit when a particular stylesheet is applied to a page.
Furthermore, the CSS width and height properties do not adjust the actual dimensions of canvas elements. Instead, they scale the element to the requested size. In your case, I'm assuming you want the canvas to actually be a different resolution. The resolution of the canvas is specified via the DOM width and height attributes on your <canvas> tag.
In order to handle resizing, you will need to use window.onresize to capture the resize event. Your canvas code will need to then create a new canvas at the desired size and properly copy over everything from the original canvas (when you resize a canvas object its pixel data is cleared).
As was yet pointed by Xenethyl, the most important point is to hook onresize so that you can adapt to your new canvas object size :
adjust the canvas dimensions (the drawing area dimensions) to the canvas rendering area (clientWidth and clientHeight)
take into account the new dimensions of the canvas for your drawing algorithms
redraw the canvas
You don't have to make a new canvas (which would force you to rehook other event handlers).
Most of the canvas in my web applications, in order to be perfectly adjusted to the window, are managed by a dedicated class whose skeleton is here :
function Grapher(options) {
this.graphId = options.canvasId;
this.dimChanged = true; // you may remove that if you want (see above)
};
Grapher.prototype.draw = function() {
if (!this._ensureInit()) return;
// makes all the drawing, depending on the state of the application's model
// uses dimChanged to know if the positions and dimensions of drawed objects have
// to be recomputed due to a change in canvas dimensions
}
Grapher.prototype._ensureInit = function() {
if (this.canvas) return true;
var canvas = document.getElementById(this.graphId);
if (!canvas) {
return false;
}
if (!$('#'+this.graphId).is(':visible')) return false;
this.canvas = canvas;
this.context = this.canvas.getContext("2d");
var _this = this;
var setDim = function() {
_this.w = _this.canvas.clientWidth;
_this.h = _this.canvas.clientHeight;
_this.canvas.width = _this.w;
_this.canvas.height = _this.h;
_this.dimChanged = true;
_this.draw(); // calls the function that draws the content
};
setDim();
$(window).resize(setDim);
// other inits (mouse hover, mouse click, etc.)
return true;
};
In your case I would create a new Grapher({canvasId:'#canvas'}) and the #canvas dimensions are defined in css (and usually adjust in complex ways to the available space).
The most interesting points are in the setDim function.

Scale and crop image in GWT

I'm trying to implement an image gallery with GWT. My intention is to show a list of albums, when you click on one of them show a grid with the photo's thumbnails on it and, when you click on one of them, show the full-size image in a popup window. I've succeeded on the first and last points, but I'm getting a lot of problems building the thumbnails.
My idea is to have squared, fixed size thumbnails. To do so, I try to scale and then crop them. But it doesn't work. If I only scale them it works, if I only crop them it works, but when I try to scale&crop only the last operation is executed. This is what I coded to do that:
prop = (float)img.getWidth() / img.getHeight();
thumb = new Image(img.getUrl());
if (thumb.getWidth() > thumb.getHeight()) {
thumb.setHeight("150px");
thumb.setWidth(String.valueOf(150 * prop) + "px");
} else {
thumb.setWidth("150 px");
thumb.setHeight(String.valueOf(150 * Prop) + "px");
}
offset = thumb.getHeight()*(prop-1) / 2;
if (thumb.getWidth() > thumb.getHeight()) {
thumb.setVisibleRect((int)offset, 0, 150, 150);
} else {
thumb.setVisibleRect(0, (int)offset, 150, 150);
}
I've been looking for help about this and I found several solutions. Based on another question in this website I found this, but it's not applicable to my case, because I need to load the images dinamically. I also tried with GWT and HTML5, following this example, but when the following method is executed:
ImageData imageData = context.getImageData(sx, sy, sw, sh);
it raises the following exception:
(NS_ERROR_DOM_INDEX_SIZE_ERR): Index or size is negative or greater than the allowed amount
So, if anyone knows how to do it with pure GWT, or solve de HTML5 error, I will be very pleased, I've been stuck on this for many weeks :(... thanks in advance!