Tuesday 4 March 2014

Image crop control for Silverlight or Windows Phone

This example gives the basis of an image cropping control for Silverlight or Windows Phone (7 or 8). It produces output as left, right, top and bottom trim %s for generic use. It would be trivial to perform the actual trimming with a library such as WriteableBitmapEx. I have made no effort to tidy this code to be more efficient or readable, but there are many such modifications that could be made.

This code allows for the phone to be used in either portrait or landscape mode. Aspect ratio is not locked, but the code could be tweaked to enforce this.

Try it out here in my WP8 Meme Maker app.


How it works
Two images sit within a grid, with the semi-transparent instance behind in z-order. A rectangle is positioned in each corner. The topmost image is clipped to a rectangle defined by dragging the rectangles around.


XAML:


<Grid>
 
            <Grid  HorizontalAlignment="Center" VerticalAlignment="Center">
 
                <Image Source="Assets/someImage.png" IsHitTestVisible="False" Opacity="0.3" ></Image>
                <Image Name="imgSauce" Source="Assets/someImage.png" >
                    <Image.Clip>
                        <RectangleGeometry x:Name="clipRect" ></RectangleGeometry>
                    </Image.Clip>
                </Image>
                <Rectangle Name="rectTopLeft" Width="20" Height="20" Margin="-10" Fill="Yellow" HorizontalAlignment="Left" VerticalAlignment="Top" >
                    <Rectangle.RenderTransform>
                        <TranslateTransform></TranslateTransform>
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Name="rectTopRight" Width="20" Height="20" Margin="-10" Fill="Yellow" HorizontalAlignment="Right" VerticalAlignment="Top" >
                    <Rectangle.RenderTransform>
                        <TranslateTransform></TranslateTransform>
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Name="rectBotLeft" Width="20" Height="20" Margin="-10" Fill="Yellow" HorizontalAlignment="Left" VerticalAlignment="Bottom" >
                    <Rectangle.RenderTransform>
                        <TranslateTransform></TranslateTransform>
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Name="rectBotRight" Width="20" Height="20" Margin="-10" Fill="Yellow" HorizontalAlignment="Right" VerticalAlignment="Bottom" >
                    <Rectangle.RenderTransform>
                        <TranslateTransform></TranslateTransform>
                    </Rectangle.RenderTransform>
                </Rectangle>
            </Grid>
        </Grid>

Code Behind:

using System;
using System.Windows;
using Microsoft.Phone.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

public partial class CropPage : Page
    {
 
        private Rectangle _draggedRect = null;
        
 
        public CropPage()
        {
            InitializeComponent();
 
            
            var rects = new Rectangle[] { rectTopRight, rectTopLeft, rectBotRight, rectBotLeft };
            Point _dragOrigin =new Point();
            double origLeftPerc= 0, origRightPerc = 0, origTopPerc = 0, origBotPerc = 0;
 
            var setOrigin = new Action<Point>((p) => {
                _dragOrigin = p;
                    origLeftPerc = this._clipLeftPerc;
                    origRightPerc = this._clipRightPerc;
                    origTopPerc = this._clipTopPerc;
                    origBotPerc = this._clipBotPerc;
            });
 
            foreach (var aRect in rects)
            {
                aRect.MouseLeftButtonDown += (s, e) => {
                    var r = (Rectangle)s;
                    _draggedRect = r;
                    setOrigin( e.GetPosition(this.imgSauce));
                    
                    r.CaptureMouse();
                };
 
                aRect.MouseLeftButtonUp += (s, e) => {                    
                    _draggedRect = null;
                };
 
                aRect.MouseMove += (s, e) => {
                    if (_draggedRect != null) {
 
                        var pos = e.GetPosition(this.imgSauce);
 
                        if (s == this.rectTopLeft || s == this.rectTopRight) {
                            // Adjust top
                            _clipTopPerc = origTopPerc + (pos.Y - _dragOrigin.Y) / imgSauce.ActualHeight;
                        }
                        if (s == this.rectTopLeft || s == this.rectBotLeft) {
                            // Adjust Left
                            _clipLeftPerc = origLeftPerc + (pos.X - _dragOrigin.X) / imgSauce.ActualWidth;
                        }
                        if (s == this.rectBotLeft || s == this.rectBotRight) {
                            // Adjust bottom
                            _clipBotPerc = origBotPerc - (pos.Y - _dragOrigin.Y) / imgSauce.ActualHeight;
                        }
                        if (s == this.rectTopRight || s == this.rectBotRight) {
                            // Adjust Right
                            _clipRightPerc = origRightPerc - (pos.X - _dragOrigin.X) / imgSauce.ActualWidth;
                        }
 
                        this.updateClipAndTransforms();
                    }
                };
            }
 
            var draggingImg = false;
 
            imgSauce.MouseLeftButtonDown += (s, e) => {
               setOrigin( e.GetPosition(this.imgSauce));
                imgSauce.CaptureMouse();
                draggingImg = true;
            };
 
            imgSauce.MouseLeftButtonUp += (s, e) => {
                draggingImg = false;
            };
 
            imgSauce.MouseMove += (s, e) => {
                if (draggingImg) {
                    var pos = e.GetPosition(this.imgSauce);
 
                    var xAdjust = (pos.X - _dragOrigin.X) / imgSauce.ActualWidth;
                    var yAdjust = (pos.Y - _dragOrigin.Y) / imgSauce.ActualHeight;
 
                    _clipLeftPerc = origLeftPerc + xAdjust;
                    _clipRightPerc = origRightPerc - xAdjust;
                    _clipTopPerc = origTopPerc + yAdjust;
                    _clipBotPerc = origBotPerc - yAdjust;
 
                    this.updateClipAndTransforms();
                }
            };
 
            imgSauce.SizeChanged += (x,y) => {
                this.updateClipAndTransforms();
            };
 
            this.updateClipAndTransforms();
        }
 
 
 
        private double _clipLeftPerc, _clipRightPerc, _clipTopPerc, _clipBotPerc =  0;
 
        void updateClipAndTransforms()
        {
            // Check bounds
            if (_clipLeftPerc + _clipRightPerc >= 1)
                _clipLeftPerc = (1 - _clipRightPerc) - 0.04;
            if (_clipTopPerc + _clipBotPerc >= 1)
                _clipTopPerc = (1 - _clipBotPerc) - 0.04;
 
            if (_clipLeftPerc < 0)
                _clipLeftPerc = 0;
            if (_clipRightPerc < 0)
            _clipRightPerc = 0;
            if (_clipBotPerc < 0)
                _clipBotPerc = 0;
            if (_clipTopPerc < 0)
                _clipTopPerc = 0;
            if (_clipLeftPerc >= 1)
                _clipLeftPerc = 0.99;
            if (_clipRightPerc >= 1)
                _clipRightPerc = 0.99;
            if (_clipBotPerc >= 1)
                _clipBotPerc = 0.99;
            if (_clipTopPerc >= 1)
                _clipTopPerc = 0.99;
            
 
            // Image Clip
            var leftX = _clipLeftPerc * this.imgSauce.ActualWidth;
            var topY = _clipTopPerc * this.imgSauce.ActualHeight;
            
            clipRect.Rect = new Rect(leftX, topY, (1 -_clipRightPerc) * this.imgSauce.ActualWidth - leftX, (1 - _clipBotPerc) *  this.imgSauce.ActualHeight - topY);
 
            // Rectangle Transforms
            ((TranslateTransform)this.rectTopLeft.RenderTransform).X = clipRect.Rect.X;
            ((TranslateTransform)this.rectTopLeft.RenderTransform).Y = clipRect.Rect.Y;
            ((TranslateTransform)this.rectTopRight.RenderTransform).X = -_clipRightPerc * this.imgSauce.ActualWidth;
            ((TranslateTransform)this.rectTopRight.RenderTransform).Y = clipRect.Rect.Y;
            ((TranslateTransform)this.rectBotLeft.RenderTransform).X = clipRect.Rect.X;
            ((TranslateTransform)this.rectBotLeft.RenderTransform).Y = - _clipBotPerc *  this.imgSauce.ActualHeight;
            ((TranslateTransform)this.rectBotRight.RenderTransform).X = -_clipRightPerc * this.imgSauce.ActualWidth;
            ((TranslateTransform)this.rectBotRight.RenderTransform).Y = -_clipBotPerc * this.imgSauce.ActualHeight;
        }
 
    }

Wednesday 27 February 2013

Windows Store app Lock Screen Badge Icon fix

There is a requirement in Windows 8 Store apps that lock screen badges be white only, with varying transparency levels. Visual Studio doesn't enforce this; you only find out when you run the App Cert Kit and your app fails with:

Image reference "Foo.png": The image "\Foo.png" has an ABGR value "0x1CAAAAAA" at position (0, 0) that is not valid. The pixel must be white (##FFFFFF) or transparent (00######).

You then jump on Google and find this or this.

Manually altering 24*24 pixels individually is not my cup of tea.

Here is some trivial VB.Net code that will take any image and convert it to a format suitable to use for a lock screen badge icon (ie every pixel has RGB of FFFFFF, with a varying A). You can then use Paint.Net or whatever to resize the image.


Imports System.Drawing
Private Function conv2Badge(img As Bitmap)
    Dim destImage = New Bitmap(img.Width, img.Height, Imaging.PixelFormat.Format32bppArgb)
    For x = 0 To img.Width - 1
        For y = 0 To img.Height - 1
            Dim srcCol = img.GetPixel(x, y)

            Dim avgInt = (((CInt(srcCol.R) + CInt(srcCol.G) CInt(srcCol.B)) / 3) * CInt(srcCol.A)) / 255

            Dim destCol = Color.FromArgb(CByte(avgInt), Color.White)

            destImage.SetPixel(x, y, destCol)
        Next
    Next

    Return destImage
End Function

Saturday 8 May 2010

AJAX Control Toolkit HTMLEditor - buttons with jQuery

Have you tried adding buttons to the toolbar of the HTML Editor in the Ajax Control Toolkit? If so, you will know it is a "mission". I gave up and built my own with jQuery. To use, just add your own buttons to the "images" div...

Hopefully modifying the script for your own purposes is relatively painless; I think the code is mostly self-documenting??


<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="HTMLEditorButtons.aspx.vb" Inherits="Foo.HTMLEditorButtons" %>


 


<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc2" %>


 


<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit.HTMLEditor"


    TagPrefix="cc1" %>


 


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


 


<html xmlns="http://www.w3.org/1999/xhtml" >


<head id="Head1" runat="server">


    <title></title>


</head>


<body>


 


    <script src="../Script/jquery-1.3.2.js" type="text/javascript"></script>


    <form id="form1" runat="server">


        <cc2:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">


        </cc2:ToolkitScriptManager>


    <div>


        <cc1:Editor ID="Editor1" runat="server" Height="400px"


            onclientactivemodechanged="modeChanged" />


 


    </div>


 


    <div id=images style="display: none; position: absolute; border: solid 1px black; background-color: White; overflow: scroll; cursor: hand;height: 150px; width:80px ">


        <img src="../Images/Stuffed_Folder.png" /><br />


        <img src="../Images/User_Complete.gif" /><br />


        <img src="../Images/Move.gif" /><br />


        <img src="../Images/help.gif" />


    </div>


 


    </form>


 


 


    <script language=javascript>


        $(document).ready(function() {


 


            $(".ajax__htmleditor_editor_toptoolbar > div").append('<img class="ajax__htmleditor_toolbar_button customButton" title="Images" id="btnImages" alt="" src="../images/picture.png" onclick="showImages()" />');


 


 


 


            $("#images").mouseleave(hideImageList);


            $("iframe").mouseover(hideImageList);


 


            $("#images img").click(function(e) {


                var editor = $find('Editor1')


                editor._editPanel._modePanels[0].insertHTML("<img src='" + $(this).attr("src") + "' />");


                hideImageList();


            }).css("cursor", "pointer");


 


            $(".customButton").mouseenter(function() {


                $(this).addClass("ajax__htmleditor_toolbar_button_hover");


            });


 


            $(".customButton").mouseleave(function() {


                $(this).removeClass("ajax__htmleditor_toolbar_button_hover");


            });


 


        });


 


        function showImages() {


            var imgPos = $("#btnImages").offset();


            $("#images").css("top", imgPos.top + 23).css("left", imgPos.left);


            $("#images").slideToggle(100);


 


        }


 


        function hideImageList() {


            $("#images").fadeOut(100);


 


        }


 


        function modeChanged(e) {


            if (e._activeMode == 0) {


                // show custom buttons


                $(".customButton").show();


            } else {


                $(".customButton").hide();     


            }


 


        }


    </script>


</body>


</html>


 


 


Tuesday 2 February 2010

Online Javascript Hex viewer - Local

Check it out, similar functionality to XVI32, but written entirely in Javascript. Uses new HTML5 methods to access local files, so no transfer of data to the server necessary. No easy means of writing file back to disk via Javascript yet, so view only for now, no edit :(

Unfortunately only works on Firefox 3 at the moment, but expect other browsers will adopt these standards reasonably quickly.

Can be a bit slow on old computers...



Monday 2 November 2009

Linq GetEnumerator.reset and new MS Chart charting controls

So I thought I would try out the new Microsoft charting controls for ASP.Net and winforms...

Looks fantastic but when I came to do some runtime databinding to a simple linq collection (selecting an x and y value into an enumerable anonymous type...) it failed miserably with an obscure error message.

With a bit of snooping I found that the problem is that the chart control always calls .Reset() on its IEnumerable(Of T) datasource before iterating the collection. Unfortunately the System.Linq.Enumerable.Iterator(Of TSource) that Linq returns as a concrete IEnumerable throws a "NotImplementedException" in its implementation of IEnumerable.Reset() - good one MS! (Check it out in Reflector)

Simple answer - call .ToArray() on your Linq collection and hey presto your Linq query will work as a chart datasource.

UPDATE - Click here and vote for MS to fix this bug!!!

Friday 13 March 2009

An Improved MsgBox for Asp.Net

Often times a simple messagebox is required at the client under ASP.Net. This is easy to do by sending back a JavaScript Alert(). However, this does not work so well if the user navigates around using the back and forward buttons, as they will see the message multiple times. Also, we would like our msgbox to be usable from AJAX enabled apps, so that an asynchronous postback can also trigger an alert.

The below vb.net 3.5 code meets these criteria:




Module PageExtensions
 
    <extension()> _
    Public Sub MsgBox(ByVal aPage As Page, ByVal aMessage As String)

        Dim timeStr As String = "msgbox" & Date.Now().Ticks.ToString

        ' Set a cookie after showing alert so as will not re-show if back button or refresh is used.

        Dim alertScript As String = "if (document.cookie.indexOf('" & timeStr & "') == -1) {"
        alertScript &= "alert(""" & aMessage & """);"
        alertScript &= "document.cookie = '" & timeStr & "=deleted';}"

        System.Web.UI.ScriptManager.RegisterStartupScript(aPage, GetType(String), aMessage, alertScript, True)

    End Sub

End Module


To use within a page, you can now simply type:
MsgBox("Hello World"), just like in a Windows App.

A temporary cookie is set for each msgbox invoked so that the msgbox will only be shown once. The method is an extension method of the Page class, so needs to be contained within a Module, or used without the attribute.

Note also you will need a reference to System.Web.Extensions in your project.