Bir Resmi Dinamik Olarak Url Parametreleri İle Resize Etme
Selamlar,
Bugün kaynağı verilen bir resmin istenen boyutlarını, Url’de belirleyip, memory’de resize edeceğiz. Buna Dynamically Resize Image On-The-Fly deniyor. Yani ilgili image’i herhangi bir fiziksel alana yazmadan, yeninden boyutlandırıyoruz.
Image Source: https://www.hanselman.com/blog/content/binary/Windows-Live-Writer/fcafa28f54d3_C700/image_9.png
Öncelikle ilgili static file’a browserda erişilmeye çalışıldığı zaman, ilgili Action’a gidilebilmesi için “CustomRouteHandler” tanımlanması gerekmektedir. Ayrıca Routing için de başdaki default “images” keyword’ünün ardından “{*ImageName}” şeklinde istenen uzunlukta text’in yazılabileceği alan tanımlanmıştır.
RouteConfig:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.Add(new Route("images/{*ImageName}", new CustomPNGRouteHandler())); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } |
Şimdi sıra geldi custom olarak tanımlanan “CustomPNGRouteHandler” ‘ın oluşturmaya. Yukarıdaki resimde de görüldüğü gibi, çalışma adımlarında “IHttpHandler” “Page,Controller ve HttpHandler”‘ın hepsini kapsamakta ve tüm adımların temelini oluşturmaktadır. Yani tüm requestler “IHttpHandler“‘dan geçer. Bunu sakın unutmayın. “IRouteHandler” interfaceinden türetilen ilgili “CustomPNGRouteHandler” class’ı aşağıdaki gibi oluşturulur. Ayrıca daha en başta ilgili route’a gelindiği zaman, bir takım işlem adımlarına ait istenen kodun yazılabilmesi için “IHttpHandler”‘dan türetilmiş “CustomPNGHandler” sınıfı oluşturulmuştur.
CustomPNGRouteHandler:
1 2 3 4 5 6 7 |
public class CustomPNGRouteHandler : IRouteHandler { public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext) { return new CustomPNGHandler(requestContext); } } |
CustomPNGHandler: İşte bütün bussines’ın döndüğü yer burasıdır. Şimdi adım adım neler olur inceleyelim:
- Kısım SEO amaçlı resimin önüne konmuş olan bir Url Title’ın olup olmadığına bakılır. Var ise temizlenip, ham image ismi uzantısı ile “RealFileName” değişkenine atanır. Örnek: http://localhost:11590/images/cdn/seo-amacli-deneme-resmi-bear.jpg?w=600&h=400 . Yandaki koyu renkli text kaldırılıp, sadece resmin adı alınır. Ayrıca “imagePath“‘e resmin yolu atanır.
- Kısım Image’in gerçekten var olup olmadığına bakılır. Eğer yok ise defualt belirlenen bir resim atanır. Ayrıca eğer resmin adının başında herhangi bir SEO tanımlaması yoksa yine resmin var olup olmadığı durumlarına da burada bakılır.
- Kısım Bu bölümde “?w=600&h=300” gibi image’in resize edilmesi istenen boyutları url’den pars edilir: İlgili url 2 farklı yolla pars edilmiştir. 1. yöntemde Linq ve Regex kullanılmıştır. 2. yöntem de ise “char.IsDigit()” methodu ile sayısal alanlar alınmıştır. İlgili resize işlemi için gerekli “width ve height” değerlerinin alımında, 3 farklı durum söz konusudur:
- 1. durum “width” ve “height“2 değerin de alınması ve “ResizeImage()” methodunun çağrılması.
- 2. durum “width” ve “height” değerlerinden sadece 1 değerin gelmesi ve gelen değerin “0”‘dan büyük olması durumudur. Bu durumda “width ve height” değerlerinden hangisinin geldiğine bakılıp, gelmeyen değere “0” atanır ve “ResizeImage()” methodu çağrılır.
- 3. durumda herhangi bir resize değeri gönderilmediği için, resmin orjinal boyutları ile ekrana basılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
public class CustomPNGHandler : IHttpHandler { public bool IsReusable { get { return false; } } protected RequestContext RequestContext { get; set; } public CustomPNGHandler() : base() { } public CustomPNGHandler(RequestContext requestContext) { this.RequestContext = requestContext; } public void ProcessRequest(HttpContext context) { //using (Bitmap image = new Bitmap("c:\\" +RequestContext.RouteData.Values["ImageName"])) #region 1.Kısım string filePath = string.Empty; //Gerçek resim ismi bulunur. Eğer başında Seo amaçtı girilen title var ise kaldırılır. //Örnek: http://localhost:11590/images/cdn/seo-amacli-deneme-resmi-bear.jpg?w=600&h=400 string fullFileName = RequestContext.RouteData.Values["ImageName"].ToString(); int startPoint = fullFileName.LastIndexOf('-') + 1; if (startPoint > 0) { int length = fullFileName.Length - startPoint; string RealFileName = fullFileName.Substring(startPoint, length); string imagePath = fullFileName.Substring(0, fullFileName.LastIndexOf('/') + 1); #endregion #region 2.Kısım if (File.Exists("c:\\" + imagePath + RealFileName)) { filePath = "c:\\" + imagePath + RealFileName; } else { filePath = "c:\\default/default.jpg"; } } else //If Not SEO Title Exist { if (File.Exists("c:\\" + RequestContext.RouteData.Values["ImageName"])) { filePath = "c:\\" + RequestContext.RouteData.Values["ImageName"]; } else { filePath = "c:\\default/default.jpg"; } } #endregion #region 3.Kısım using (var image = new Bitmap(filePath)) { var fileInfo = new FileInfo(filePath); context.Response.ContentType = string.Concat("image/", fileInfo.Extension.Replace(".", "")); var urlParams = context.Request.Url.Query; //1. Yol int[] numbers = (from Match m in Regex.Matches(urlParams, @"\d+") select int.Parse(m.Value)).ToArray(); //2. Yol /*int width = int.Parse(new string(param.SkipWhile(x => !char.IsDigit(x)) .TakeWhile(char.IsDigit).ToArray()));*/ if (numbers.Length> 1 && (numbers[0]>0 || numbers[1]>0)) { int width = numbers[0]; int height = numbers[1]; ResizeImage(image, new Size(width, height)).Save(context.Response.OutputStream, image.RawFormat); } else if (numbers.Length == 1 && numbers[0] > 0) { int width = urlParams.Contains("w=")?numbers[0]:0; int height = urlParams.Contains("h=")?numbers[0]:0; ResizeImage(image, new Size(width, height)).Save(context.Response.OutputStream, image.RawFormat); } else { image.Save(context.Response.OutputStream, ImageFormat.Png); } } #endregion } |
ResizeImage: İlgili image’in url’de belirlenen boyutlara göre boyutlandırıldığı yer burasıdır. Burda da 3 durum söz konusudur:
- Durum : En başta ilgili image’in Aspect Ratio’su “dblRatio” değişkenin atanır. Eğer gelen image boyutlarından birisi “0” ise bu değer, ilgili image’in bulunan Aspect Ratio oranı ile bilinen boyutuna göre hesaplanır.
- Eğer image’in resize amaçlı belirtilen boyutlarından biri “width veya height” image’in gerçek boyutundan büyük ise, kala alınmaz ve işleme image’in orjinal boyutuna göre devam edilir.
- Eğer belirtilen yeni boyutlar image’in yeni aspect ratio’suna uymuyor ise, büyük olan boyut sabit alınarak, diğer boyut aspect ratio oranına göre hesaplanır ve resize işlemi bu yeni boyutlara göre yapılıp oluşturulan yeni “Bitmap” geriye döndürülür.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public Bitmap ResizeImage(Bitmap imgToResize, Size size) { try { //Image'in Aspect Ratio Oranını Bul double dblRatio = (double)imgToResize.Width / (double)imgToResize.Height; //1-) Gelen Width ve Height Değerlerinden "0" Olan Var ise, buna karşılık gelen değer, Image'in Aspect Ratio Oranına Göre Bulunur. size.Width=size.Width == 0 ? (int)(size.Height * dblRatio) : size.Width; size.Height = size.Height == 0 ? (int)(size.Width/ dblRatio) : size.Height; //2-) Eğer olması istenen boyutlardan biri, image'in gerçek boyutundan büyük ise, image'in orjinal boyutuna göre işleme devam edilir. size.Width = size.Width > imgToResize.Width ? imgToResize.Width : size.Width; size.Height = size.Height > imgToResize.Height ? imgToResize.Height : size.Height; //3-) Eğer belirtilen yeni boyutlar image'in yeni aspect ratio'suna uymuyor ise, büyük olan boyut sabit alınarak, diğeri aspect ratio oranına göre bulunur. double dblResizeRatio = (double)size.Width / (double)size.Height; if(Math.Abs(dblResizeRatio- dblRatio)>0.01) { //Büyük olan oranı bul ve diğerini Aspect ratio oranını koruyarak değiştirilir. if (size.Width > size.Height) { size.Height = (int)(size.Width / dblRatio); } else { size.Width = (int)(size.Height * dblRatio); } } Bitmap b = new Bitmap(size.Width, size.Height); using (Graphics g = Graphics.FromImage((Image)b)) { g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.DrawImage(imgToResize, 0, 0, size.Width, size.Height); } return b; } catch { Console.WriteLine("Bitmap could not be resized"); return imgToResize; } } } |
Web.config: Tüm static filelara erişim yetkisi “runAllManagedModulesForAllRequests=true” değeri atanarak verilir. Yani *.* PNGs, PDFs, gibi tüm filelar da ASP.NET’in full pipe line’ına katılır. Eğer bu işlemi IIS tarafında halledilir ise çok daha performanslı bir iş yapmış olunur. Aşırı yük testi yapılarak sayfanın takip edilebilmesi için “ApplicationInsightsWebTracking” projeye eklenmiştir.
1 2 3 4 5 |
<modules runAllManagedModulesForAllRequests="false"> <remove name="ApplicationInsightsWebTracking"/> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler"/> </modules> |
Geldik bir makalenin daha sonuna. Bu makalede static bir resmin, yerine göre nasıl farklı boyutlarda Aspect Ratio oranı bozulmadan Resize edilebileceğini ve static bir dosyaya Mvc Custom Routing ile nasıl erişilebileceğini hep beraber inceledik. Bu makalenin işinize yaramasını temenni eder, şimdiden başarılar dilerim.
Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source Code: https://github.com/borakasmer/OnMemoryResizeImage
Kaynaklar : Scott Hanselman Blog, Msdn Magazine
Paylaşımınız için teşekkürler Bora bey. Aynı işlemi yapan hazır bir bileşen de mevcut.
http://imageresizing.net/
Teşekkürler Fatih,
Imagelerin başına SEO amaçlı url text desteği veren bir servis şu anda yok.
resmin adını bu-benim-resmim.jpg olarak kaydedersek ve uygulamada src=”bu-benim-resmim.jpg?w=100″ olarak kullanırsak olur sankimsi
Selam Ferih,
İşte burada amaç resmin adını o şekilde değiştirmemek. Bu makalede anlattığım örnekde, resmin önüne “-” istenen herşey yazılabiliyor. Mesela haber title. Hem de resmin adından bağımsız :) Bu gerçekten önemli bir konu. Tüm resimlerin ismini haber title yapamazsınız. Hatta yapmamalısınız. Haberin başlığını değişince resmin adınıda mi değiştiriceğiz :) Seo amaçlı resim adları kısaca önemli.
İyi çalışmalar.
kolay gelsin yazı için elinize sağlık benim konuyla alakasız bir sorum olacaktı sizin tecrübelerinize göre kolay bir soru olabilir kusura bakmayın haber sitelerinde veya blog sitelerinde kullanılan text editorlerde kullanılan alana script yazarak bir açık oluşabiliyor bu açığı nasıl kapatılıyor teşekkürler
Selam Ferih,
Aslında birçok yolu var. Mesela şu şekilde ‘@RenderSection(“body_scripts”, false)’önlem alabilirsin.
‘DynamicPNGs’ dosyasını veya bütünleştirilmiş kodunu ya da bağımlılıklarından birini yükleyemedi. Sistem belirtilen dosyayı bulamıyor.
DynamicPNGs yerine namespace ismini yazıp hallettim fakat bu sefer de şöyle bir sorun var. Login olurken bilgileri [HttpPost] attributene sahip login action ını çağırdığımda CustomPNGRouteHandler:IRouteHandler class içersindeki GetHttpHandler fonksiyonu tetikleniyor. CustomPNGRouteHandler class ı içersinde GetHttpHandler fonksiyonunda ImageName değeri null geliyorsa CustomPNGHandler classını kullanmadan sistemin rutin şekilde çalışmasını istiyorum. Araştırdığımda context.Response.Redirect(“http://www.siteadi.com”, true); şeklinde bir çözüm var fakat o request içersinde benim kullanıcı adı ve şifre bilgilerim de mevcut
Kısacası kullanıcı adı ve şifre bilgilerini girip submit ile post ettiğimde => http://localhost:51131/images?action=login&controller=panel göyle bir ur tetikleniyor. GetHttpHandler içersinde return new DefaultHttpHandler() da denedim yine olmadı. Konu ile ilgili çözümü bir yandan da araştırıyorum. Eğer daha iyi ve kolay bir yolu varsa açıklayabilirseniz sevinirim
Bora kardeşim selam, öncelikle eline sağlık çok güzel bir modül olmuş ve kullanıyorum. Yalnız küçük resimlerin görsel kalitesini biraz daha yüksek gösterme şansımız yani böyle bir parametre ekleme imkanımız var mı?
Selam Cihan,
Var olan malzeme belli. Bir resmi var olan çözünürlüğünden daya yüksek çözünürlükte ve en az aynı kalitede göstermek için onu yeniden işlemelisin. Yani o artık aynı resim değildir. Bunun için özel gelişmiş resim uygulamarı var. Resmi yeniden pixel pixel render edip oluşturuyor. Uzun lafın kısası var olan çözünürlükten daha küçük çözünürlüklere çevirebilir ama daha yüksek çözünürlüklere getirmek için gelişmiş bir program yazmalısın. Parametre ile çözülebilecek bir durum yok.
İyi çalışmalar.
Bora selam tekrar, ben o anlamda dememiştim aslında. 1500×1500 bir imajı w=300 diye çağırınca oluşan kötü bir görüntü olabiliyor bu yüzden işleme kalitesini artıracak bir parametre nereye eklenebilir senin kodunda diye sormuştum aslında. HighQualityBicubic gibi bir parametre var aslında ama yine de bozabiliyor. Sonrasında ben onu cevabın mail olarak gelecek diye düşündüğüm için görmedim ve saçma da olsa şu şekilde bir yöntem bularak hallettim kendimce. <img width=300 kullanıyorsam için <img src="xx.jpg" olarak çektiğim imaja ?w=500 değeri verdim böylece daha kaliteli imaj çekmiş oldum. Biraz kulağı tersten tutmak gibi oldu ama işe yaradı. Teşekkürler :)
Bir de her seferinde tekrar yüklüyordu resimleri, ben ProcessRequest 3.kısımda context.Response.ContentType satırının altına aşağıdaki 2 satırı ekleyip en azından client tarafında cache’lenmesini sağladım.
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1));
Sorum ise bu yaratılan imajları herkes için yaratılmadan sunucu tarafında cachelenmesi nasıl sağlanabilir birkaç şey denedim ama uyum sağlamadı. 1-2 satır ekleyerek bunu sağlamanın kolay bir yolu var mı yoksa uzun uzun sunucuda bir cache klasör oluşturup oraya kaydetmesini sağlayıp ara ara burayı kontrol edip yenileyecek bir sistem mi kurmak gerekir?
Yardımcı olabilirsen sevinirim, teşekkürler.
Cihan
IIS Cache, ya da bir CDN ile çalışmak gazel güzel bir yöntem olabilir…