Cropper світлин сайту.
На цій сторінці я розповім про свій кроппер для світлин сайту. Взагалі кроппер має серверну і клієнтську частину. Почнемо з клієнтської частини. Взагалі клєнської частини кропперів існує безліч, но мені більш за все подобається Tapmodo-Jcrop, саме його ви бачите зліва. Клієнтська частина цього кроппера - це jQuery (а взагалі може бути що завгодно, від JAVA, javascript та SilverLight до Flex та Flash). Тут важливо зрозуміти те, що не розуміють "дикі люди", що працюють на PHP. jQuery і javascript - це зовсім інші технології. Тому що дуже часто на сторінці ASP.NET присутній MS AJAX, який не дозволяє працювати jQuery. Точніше дозволяє, як хандлер Sys.WebForms.PageRequestManager.getInstance().add_endRequest - єле коли ви спробуєте їх зростити... єле це окрема тема. На щастя, останній час мої проєкти на ASP.NET MVC, там немає різниці між jQuery і javascript.
Щоб додати кроппер до сторінки потрібно просто додати до сторінки виклик скриптів.
1: <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
2: <script type="text/javascript" src="../../tapmodo-Jcrop/js/jquery.Jcrop.js"></script>
3: <script type="text/javascript">
4:
5:
6: jQuery(function ($) {
7:
8: var jcrop_api;
9:
10: $('#target').Jcrop({
11: onChange: showCoords,
12: onSelect: showCoords,
13: onRelease: clearCoords
14: }, function () {
15: jcrop_api = this;
16: });
17:
18: $('#coords').on('change', 'input', function (e) {
19: var x1 = $('#x1').val(),
20: x2 = $('#x2').val(),
21: y1 = $('#y1').val(),
22: y2 = $('#y2').val();
23: jcrop_api.setSelect([x1, y1, x2, y2]);
24: });
25:
26: var img = document.getElementById('target');
27: $('#imagewidth').val(img.width);
28:
29: });
30:
31: // Simple event handler, called from onChange and onSelect
32: // event handlers, as per the Jcrop invocation above
33: function showCoords(c) {
34: $('#x1').val(c.x);
35: $('#y1').val(c.y);
36: $('#x2').val(c.x2);
37: $('#y2').val(c.y2);
38: $('#w').val(c.w);
39: $('#h').val(c.h);
40: };
41:
42: function clearCoords() {
43: $('#coords input').val('');
44: };
45:
46:
47:
48: </script>
Але додати готовий скриптік - це невеличка частка всієї цієї роботи. Далі на сторінці е ще щось, і про це ми поговоримо детальніше. Сам по собі малюнок тут відтворюється на сторінці хандлером, який викликаеться у 67-й стрічці.
50: <h2>Crop Foto</h2>
51:
52: <%Dim URI As String = "http://" & System.Configuration.ConfigurationManager.AppSettings("HostingURL")%>
53:
54: <form id="coords" class="coords" method="post" action="<%:URI %><%: Url.Action("Crop" & viewcontext.RouteData.Values("controller"), viewcontext.RouteData.Values("controller"))%>">
55:
56: <br />
57: <div class="inline-labels">
58: <label>X1 <input type="text" size="4" id="x1" name="x1" /></label>
59: <label>Y1 <input type="text" size="4" id="y1" name="y1" /></label>
60: <label>X2 <input type="text" size="4" id="x2" name="x2" /></label>
61: <label>Y2 <input type="text" size="4" id="y2" name="y2" /></label>
62: <label>W <input type="text" size="4" id="w" name="w" /></label>
63: <label>H <input type="text" size="4" id="h" name="h" /></label>
64: </div>
65: <br />
66:
67: <img src="<%:URI %>/GetImage.ashx?ID=<%: ViewContext.RouteData.Values("id")%>&Mode=w&w=800" id="target">
68:
69: <br /><br /><br />
70:
71: <%: Html.Hidden("id", ViewContext.RouteData.Values("id"))%>
72: <%: Html.Hidden("ret", request.QueryString("ret"))%>
73: <%: Html.Hidden("imagewidth")%>
74: <%: Html.Hidden("securitytoken1", OCMR.SecurityToken.GetToken(Model.id))%>
75:
76: <input type="submit" id="submit1" value="Crop and Return" name="submit1" style="width:200px" class="button1" />
77: </form>
78:
79: <br /><br />
Все, що робить цей скриптік, ще рахує координати від подій OnMousever і записує нові координати у поля x1,x2,y1,y2,h,w. Даді потрібно передати ці координати на сервер. Я маю стандартний інтерфейс свого графічного двигуна, він вимагає securitytoken1 та інші параметры, які ви бачите у стрічках 71-74. Чому так важно, навіщо це? А тому, що кроппер та інші графічні операції можуть працювати по AJAX без повного постбеку - і тоді усі модифікації та видалення графіки потребують не FORMS-аутентифікації сайту, а securitytoken, так як описано у мене на сторінці Сховище графіки на SQL FileStream та канал браузеру multipart/form-data.
Але ця сторінка у мене працює по повному постбеку, тому MVC-контролер лише транслює виклик до хандлеру, який додає невеличкий wrapper-service до усього мойого графичного двигуна і дозволяє викликати всі графичні функції безпосередньо з серверного контроллеру.
1: Imports System.Web
2: Imports System.Web.Services
3:
4: Public Class CropImage
5: Implements System.Web.IHttpHandler
6:
7: 'вызов хандлера не напрямую, а из MVC-контроллера
8: 'Dim CropHandler As New CropImage
9: 'CropHandler.ProcessRequest(System.Web.HttpContext.Current)
10: ''при вызове хандлера из контроллера SecurityToken в принципе не нужен - но если хандлер висит на сайте, чтобы защитить рисунки - токен нужен
11:
12: Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
13: 'x1=197&y1=128&x2=359&y2=272&w=162&h=144&id=b6b29e61-ffa8-49ec-902b-b18de2fa6550&ret=http%3a%2f%2flocalhost%3a52796%2fSupervisor%2fModerateFoto%2f05e79a33-9c06-4e48-8f32-70e2c2ace442&imagewidth=800
14: 'если параметр RET (URL куда надо перейти по окончанию обработки), то не переходим никуда, переходить будет вызывающий код контроллера
15:
16: Dim PRM = context.Request.Form
17: '
18: Dim ImageID As Guid
19: Dim Ret As String
20: 'єто непоредственно прилетает от кроппера X1,Y1 - начальная координата с левого верхнего угла
21: Dim X1 As Integer
22: Dim Y1 As Integer
23: Dim H As Integer
24: Dim W As Integer
25: Dim X2 As Integer
26: Dim Y2 As Integer
27: 'но рисунок для браузера был отмасштабирован к размеру ImageWidth, например 800
28: Dim ImageWidth As Integer
29: '
30: Dim SecurityToken As String = PRM("securitytoken1")
31: '
32: Try
33: Ret = PRM("Ret")
34: ImageID = Guid.Parse(PRM("id"))
35: '
36: X1 = CInt(PRM("x1"))
37: Y1 = CInt(PRM("y1"))
38: H = CInt(PRM("h"))
39: W = CInt(PRM("w"))
40: X2 = CInt(PRM("x2"))
41: Y2 = CInt(PRM("y2"))
42: '
43: ImageWidth = CInt(PRM("imagewidth"))
44: Catch ex As Exception
45: context.Response.ContentType = "text/plain"
46: context.Response.Write("bad parm 1")
47: End Try
48: '
49: Dim FS_DB As New OCMR_FSDataContext
50: FS_DB.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED")
51: FS_DB.CommandTimeout = 0
52: '
53: 'проверили токен
54: '
55: Dim CheckSecurityToken = FS_DB.DecryptSecurityToken(SecurityToken, ImageID)
56: If CheckSecurityToken Is Nothing Then
57: context.Response.ContentType = "text/plain"
58: context.Response.Write("bad parm 8")
59: Exit Sub
60: End If
61: If CheckSecurityToken <> ImageID Then
62: context.Response.ContentType = "text/plain"
63: context.Response.Write("bad parm 9")
64: Exit Sub
65: End If
66: '
67: 'прочитали рисунок
68: '
69: Dim CurrentFoto = (From X In FS_DB.EventFotos Select X Where X.RowGuid = ImageID).ToList
70: If CurrentFoto.Count = 0 Then
71: context.Response.ContentType = "text/plain"
72: context.Response.Write("no foto")
73: Exit Sub
74: End If
75: 'байтовый поток
76: Dim ImageBytes(CurrentFoto(0).Image.Length) As Byte
77: ImageBytes = CurrentFoto(0).Image.ToArray
78: 'размерность рисунка
79: Dim Dimension As ImageService.Dimension = (New ImageService).GetDimension(ImageBytes)
80: Dim WidthRatio As Single = Dimension.Width / 800
81: '
82: 'запомнили имена кешей
83: Dim Cache1 As String = CurrentFoto(0).Cache1
84: Dim Cache2 As String = CurrentFoto(0).Cache2
85: If Cache1 Is Nothing Then Cache1 = ""
86: If Cache2 Is Nothing Then Cache2 = ""
87: '
88: 'создали новый рисунок
89: '
90: Dim NewImage As Byte() = ImageService.Crop(ImageBytes, CInt(X1 * WidthRatio), CInt(Y1 * WidthRatio), CInt(W * WidthRatio), CInt(H * WidthRatio))
91: '
92: 'Теперь сохрании в базу новый FileStream
93: '
94: CurrentFoto(0).Image = NewImage
95: CurrentFoto(0).Heigth = Dimension.Heigth
96: CurrentFoto(0).Width = Dimension.Width
97: FS_DB.SubmitChanges()
98: '
99: 'теперь создаем новые кеши
100: '
101: Dim LastID As Integer = CurrentFoto(0).i
102: Dimension = (New ImageService).GetDimension(NewImage)
103: Dim Y As New LoadImage
104: Y.CreateCache(context, NewImage, Dimension, LastID)
105: '
106: 'удаляем старые кеши до поворота
107: '
108: Dim Dir1 As String = System.Configuration.ConfigurationManager.AppSettings("ImageCachePatch")
109: Dim FullFileName1 = IO.Path.Combine(Dir1, Cache1)
110: If My.Computer.FileSystem.FileExists(FullFileName1) Then
111: My.Computer.FileSystem.DeleteFile(FullFileName1)
112: End If
113: Dim FullFileName2 = IO.Path.Combine(Dir1, Cache2)
114: If My.Computer.FileSystem.FileExists(FullFileName2) Then
115: My.Computer.FileSystem.DeleteFile(FullFileName2)
116: End If
117: '
118: If Ret <> "" Then
119: 'перешли на заданную страничку
120: context.Response.Redirect(Ret)
121: Else
122: 'AJAX mode - ответили в браузер
123: context.Response.ContentType = "image/bmp"
124: context.Response.BinaryWrite(NewImage)
125: End If
126:
127:
128: Exit Sub
129:
130: End Sub
131:
132: ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
133: Get
134: Return False
135: End Get
136: End Property
137:
138: End Class
У стрічці 90 викликається Загально графічні функції, які і формують світлину нового розміру. Взаємодія контроллеру і хандлеру здається трошки важкою, але якщо зрозуміти, що хандлер може працювати окремо від AJAX-викликів, то все стає на місце.