Advanced Thumbnail Creator

1 Задача

Работая с новостными лентами или же просто с информацией, содержащей графические изображения, довольно часто возникает потребность генерации картинок-превью. На первый взгляд нет ничего проще чем создать уменьшенное изображение произвольного рисунка или фотографии. Однако, оказывается и в этой области есть где развернуться.

2 Существующие решения

Есть несколько способов решения данной задачи:

  1. Использование оригинального изображения в качестве превью путем его уменьшения свойствами тега <img>.
  2. Уменьшение размеров изображения при помощи функций imagecopyresampled(), imagecopyresized() библиотеки GD.
  3. Выделение центральной области изображения и ее последующее преобразование функциями библиотеки GD.
  4. Создание превью при помощи графических приложений на локальной машине и последующая загрузка на сервер.

Очевидное преимущество первых трех методов — полная автоматизация процесса. Недостатки: в первом случае изображение загружается в оригинальном размере (рис. 2а), во втором - в превью не сохраняются оригинальные пропорции изображения (рис. 2б), в третьем - центральная часть изображения не всегда является "значащей" (рис. 2в).

Четвертый способ генерации превью дает необходимые результаты (рис. 2г), но добавляет лишние хлопоты при переключении между приложениями, возникает необходимость загрузки двух файлов вместо одного (в лучшем случае).

Figure 1 - Initial image

Рисунок 1 — Исходное изображение

Рисунок 2а - Результаты генерации превью а)
рис.2а
Рисунок 2б - Результаты генерации превью б)
рис.2б
Рисунок 2в - Результаты генерации превью в)
рис.2в
Рисунок 2г - Результаты генерации превью г)
рис.2г

Рисунок 2 — Результаты генерации превью (70x70)

3 Решение

Суть метода — автоматическое создание превью с помощью php-скрипта посредством ручного выделения "значащей" области.

4 Используемые технологии

PHP, Javascript, DOM, CSS, HTML

5 Кросс-браузерность

Internet Explorer 6.0,7.0; FireFox 1.0,2.0; Opera 9.0; Safari 2.0;

6 Входные и выходные параметры

Входные параметры:

  1. $_GET[filename] — строка, определяющая имя файла и путь к нему относительно корневого каталога на сервере. Переменная $_GET[filename] должна быть предварительно закодирована функцией urlencode();
  2. $_GET[thumbwidth] — предустановленная ширина превью изображения. Если этот параметр равен "0" или не определен, ширина превью определяется в зависимости от выбранной области.
  3. $_GET[thumbheight] — предустановленная высота превью изображения. Если этот параметр равен "0" или не определен, высота превью определяется в зависимости от выбранной области.

Выходные параметры:

  1. Превью изображения на странице браузера.
  2. Файл-превью типа JPEG, находящийся в том же каталоге что и исходное изображение.

7 Реализация

Разработанный программный комплекс состоит из двух файлов.

  1. thumbcreate.js — набор функций для работы с выделяемой областью и функции подготовки генерации изображения.
  2. thumbcreate.php — содержит функцию генерации превью, блок отображения рабочей области и форму ввода-вывода параметров превью.

7.1. Описание функций модуля thumbcreate.js.

//initialization
function init()
{
  document.getElementById("image").onclick=mouseHandler;
  document.getElementById("image").onmousemove=mouseHandler;
  document.getElementById("th_w").value=thumbWidth;
  document.getElementById("th_h").value=thumbHeight;
}

//mouse handler
function mouseHandler(mouseEvent)
{
  if (!mouseEvent) mouseEvent = window.event;
  if (mouseEvent.button == 2) return;
  var element = (mouseEvent.target)?mouseEvent.target:mouseEvent.srcElement;

//for a clique we begin to draw a selection rectangle
  if (mouseEvent.type=="click")
  {
  var x = mouseEvent.clientX - document.getElementById("image").offsetLeft;
  var at = mouseEvent.clientY - document.getElementById("image").offsetTop;
  pointSet(x,y);
  rectangleDraw("area");
  };

//draw the selection region during mouse motion
  if (mouseEvent.type=="mousemove")
  {
  .
  }
}

//setting coordinates for top left and right bottom corner
function pointSet(x,y)
{
  if (!ptype)
  {
  x1=x+document.body.scrollLeft;
  y1=y+document.body.scrollTop;
  rectangleHide("area");
  inputUpdate();
  }
  else
  {
  x2=x+document.body.scrollLeft;
  y2=y+document.body.scrollTop;
  pointCorrect();
  inputUpdate();
  }
  ptype = !ptype;
}

//correcting TL BR coordinates if they are switched
function pointCorrect(x1c,y1c,x2c,y2c)
{
  .
}

//rectangle drawing (x1,y1); (x2,y2)
function rectangleDraw(rectId)
{
  .
}

// rectangle drawing from input fields
function rectangleDrawInput(rectId)
{
  .
}

//rectangle hiding
function rectangleHide(rectId)
{
  .
}

//input fields update functions
function inputUpdate()
{
  .
}

function inputWidthUpdate()
{
  .
};

function inputHeightUpdate()
{
  .
};

function inputXYUpdate()
{
  .
};

//prepare for thumbnail generation
function generateImageThumb()
{
  var previewclass="preview";
  var previewimage="/preview.gif";	

  var links=document.getElementsByTagName("a");
  var prevlinks=new Array();
  var c=0;
	
  var previewTest = new RegExp("(^|\s)" + previewclass + "(\s|$)");

  for(I=0; I<links.length; I++)
  {
  if (previewTest.test(links[I].className))
   prevlinks[c]=links[I]; c++; }
  }

  for(I=0; I<prevlinks.length; I++)
  {
  var newa=document.createElement("a");
  newa.style.textDecoration="none";

  var newbutton=document.createElement("input");
  newbutton.type="button";
  newbutton.value="Generate Thumbnail";
  
  newa.appendChild(newbutton);
  newa.href="#";

  var newbr=document.createElement("br");
  newa.appendChild(newbr);

  newa.onclick=function()
		{
  if(this.getElementsByTagName("img")[0])
  this.removeChild(this.getElementsByTagName("img")[0]);
  
  var newimg=document.createElement("img");
  newimg.style.border="0";
  newimg.vspace="10";
  this.appendChild(newimg);

  var rand=parseInt(1000*Math.random());
  newimg.src="?action=generate&r="+rand+"&x1="+document.getElementById('x1_inp').value+
  "&y1="+document.getElementById('y1_inp').value+"&x2="+document.getElementById('x2_inp').value+
  "&y2="+document.getElementById('y2_inp').value+"&w="+document.getElementById('th_w').value+
  "&h="+document.getElementById('th_h').value+"&fn="+document.getElementById('fileName').value;

  return false;
  }

  prevlinks[I].parentNode.insertBefore(newa,prevlinks[I].nextSibling);
  }

}	

//initialization
window.onload=function()
{
  init();
  generateImageThumb();
}

7.2. Описание функций модуля thumbcreate.php.

<?php
  if (!$_GET[thumbwidth]) $_GET[thumbwidth]=0;
  if (!$_GET[thumbheight]) $_GET[thumbheight]=0;

//thumbnail creation function
  function createthumb($new_w,$new_h,$x1,$y1,$x2,$y2,$fn)
  {
  $src_img=imagecreatefromjpeg($_GET['fn']);
  $dst_img=ImageCreateTrueColor($new_w,$new_h);
  imagecopyresampled($dst_img,$src_img,0,0,$x1,$y1,$new_w,$new_h,$x2-$x1,$y2-$y1); 

//output to browser
  imagejpeg($dst_img);
//output to file
  imagejpeg($dst_img,substr($_GET['fn'],0,-4)."_th.jpg"); 

  imagedestroy($dst_img); 
  imagedestroy($src_img); 
  }
  if ($_GET[action]=="generate")
  {
  header('Content-type:image/jpeg');
  createthumb($_GET['w'],$_GET['h'],$_GET['x1'],$_GET['y1'],$_GET['x2'],$_GET['y2'],$_GET['fn']);
  }
?>

//working area
<div id="image" style="cursor: crosshair; width:<?= $imgWidth ?>px; 
height:<?= $imgHeight ?>px; border-width:0; background-image: url('<?= $_GET[filename] ?>') ">
  <div id="area"></div>
</div>

//input-output form
<form action="" method="post">
<table width="<?=$imgWidth?>px">
  <tr>
   <td width="33%" align="center">
    TL (x1, y1) = (<input type="text" id="x1_inp" value="0" class="atc1" onChange="inputXYUpdate();" />,
    <input type="text" id="y1_inp" value="0" class="atc1" onChange="inputXYUpdate();" />)<br />
    BR (x2, y2) = (<input type="text" id="x2_inp" value="0" class="atc1" onChange="inputXYUpdate();">,
    <input type="text" id="y2_inp" value="0" class="atc1" onChange="inputXYUpdate();" />)<br /></td>
    <td width="33%" align="center">
    Width = 
    <input type="text" id="th_width" value="0" class="atc2" onChange="inputWidthUpdate();" /><br />
    Height = 
    <input type="text" id="th_height" value="0" class="atc2" onChange="inputHeightUpdate();" /><br /></td>
    <td width="33%" align="center">
    Thumbnail width = <input type="text" id="th_w" value="" class="atc2" /><br />
    Thumbnail height = <input type="text" id="th_h" value="" class="atc2" /><br /></td></tr>
    <tr><td colspan="3" align="center"><br /><a href="/none.gif" class="preview"></a></td></tr>
</table><input type="hidden" value="<?= $fileInput ?>" id="fileName" /></form>

8 Пример работы скрипта

Вы можете посмотреть пример в действии на этом сайте или перейти на сайт автора.

9 Ссылки

  1. Image previews with DOM JavaScript — http://icant.co.uk/articles/imagepreview/
  2. Сложное масштабирование изображений в PHP — http://www.codenet.ru/webmast/php/Image-Resize-GD/

10 Исходный код

Advanced Thumbnail Creator: atc.zip
Библиотека thumbcreate.js: thumbcreate.js
Модуль thumbcreate.php: thumbcreate.php
Описание в формате PDF: atc.pdf

Жупаненко Андрей, оригинал сатьи: http://research.zhupanenko.com/atc/.

 

Этот пример с описанием его работы прислан автором для размещения на XHTML.ru. Подробнее...



Много комментариев (20) к “Advanced Thumbnail Creator”

  1. Баранов Андрей :

    Добавлю:

    rect.style.left=coords[0]+'px';
    rect.style.top=coords[1]+'px';
    rect.style.width=coords[2]-coords[0]+'px';
    rect.style.height=coords[3]-coords[1]+'px';

    следует указывать единицы измерения, если они отличны от ‘0′, в данном случае – ‘px’. Если их нет, то при указании DOCTYPE Firefox не отображает область выделения.


  2. Алексей :

    Между прочим, такого решения с JS-выделением я не встречал! Обязательно возьму на вооружение. Но вот PHP скрипт нуждается в правильном написании :)


  3. korchasa :

    Не совсем понятно почему обязательно делать изображения 70Х70. Логичнее масштабировать до размера максимальной стороны в 70px.

    Совсем упущенны консольные утилиты, для масштабирования.

    ЗЫ: РНР-скрипт дествительно совсем “детский”.


  4. dark-demon :

    неудобно, глючно и плохо подходит, когда требуется изображение определённого размера.

    если есть желание – можете попытаться распотрошить сей примерчик: http://www.katit.ru/imageditor/389c97b8d807154bea707ee5d149e89b.jpg/


  5. Binary Look » links for 2007-03-10 :

    [...] » Advanced Thumbnail Creator – XHTML.RU Работая с новостными лентами или же просто с информацией, содержащей графические изображения, довольно часто возникает потребность генер (tags: thumbnail php webdev javascript) [...]


  6. Store :

    как по мне прикольно. уже использую в своей админке. спасибо


  7. Abrvalk :

    немного неудобно что два раза кликать надо, а так нормуль.


  8. welder :

    Напало на выходных вдохновение вот и переделал скрипт который делает миниатюры по средствам ручного выделения.

    скачать можно тут http://welder.host-expert.com/scripts_docs/files/welder_thumbnail_creator.tar.gz

    online тут http://welder.host-expert.com/demo/rezak/index.php

    документация тут http://welder.host-expert.com/demo/rezak/readme.html


  9. Строительство коттеджей :

    2 welder
    Большое спасибо за доработанный скрипт и открытые коды скрипта

    PS: отдельное спасибо за документацию по установке, обычно леняться написать


  10. welder :

    Вторая версия скрипта появмлась куча новых возможностей.
    смотреть тут http://welder.host-expert.com/demo/thumb2/
    скачивать тут http://welder.host-expert.com/hlam/thumb2.zip


  11. Creo :

    В яваскрипте следует учитывать прокрутку страницы, так как при скролле выделение происходит не там где кликает мышь.

    Юзай


    if(obj == window) {
    var myWidth = 0, myHeight = 0;
    if( typeof( window.innerWidth ) == 'number' ) {
    //Non-IE
    myWidth = window.innerWidth;
    myHeight = window.innerHeight;
    } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in 'standards compliant mode'
    myWidth = document.documentElement.clientWidth;
    myHeight = document.documentElement.clientHeight;
    } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myWidth = document.body.clientWidth;
    myHeight = document.body.clientHeight;
    }
    var scrOfX = 0, scrOfY = 0;
    if( typeof( window.pageYOffset ) == 'number' ) {
    //Netscape compliant
    scrOfY = window.pageYOffset;
    scrOfX = window.pageXOffset;
    } else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
    //DOM compliant
    scrOfY = document.body.scrollTop;
    scrOfX = document.body.scrollLeft;
    } else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
    //IE6 standards compliant mode
    scrOfY = document.documentElement.scrollTop;
    scrOfX = document.documentElement.scrollLeft;
    }
    return [scrOfX, scrOfY, myWidth,myHeight];
    }
    else if (obj == document.body) {
    if (window.innerHeight && window.scrollMaxY) {
    yScroll = window.innerHeight + window.scrollMaxY;
    xScroll = window.innerWidth + window.scrollMaxX;
    } else if (obj.scrollHeight > obj.offsetHeight){ // all but Explorer Mac
    yScroll = obj.scrollHeight;
    xScroll = obj.scrollWidth;
    } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
    yScroll = obj.offsetHeight;
    xScroll = obj.offsetWidth;
    }

    return [xScroll, yScroll];
    }
    else {
    if (obj.offsetParent) {
    curleft = obj.offsetLeft
    curtop = obj.offsetTop
    var objParent = obj;
    objParent = objParent.offsetParent;
    while (objParent) {
    curleft += objParent.offsetLeft
    curtop += objParent.offsetTop
    objParent = objParent.offsetParent;
    }
    }
    return [curleft, curtop,obj.offsetWidth,obj.offsetHeight];
    }
    }

    тоесть координата выделения будет следующая

    document.getElementById("имя_слоя").style.left=getDim(window)[0]+e.clientX+"px";
    document.getElementById("имя_слоя").style.top=getDim(window)[1]+e.clientY+"px";

    Данный код является совместимым с IE,FireFox,Opera. И отвечает стандартам XHTML 1.0 Strict


  12. hlomzik :

    Извините, но разговор вообще ни о чем – юзабилити ноль во всех предложенных здесь вариантах, кроме варианта dark-demon’а.
    Постараюсь в скором времени оформить для выкладывания свой скрипт и поделюсь. Единственный открытый вариант уже довольно устарел, но все равно, на мой взгляд, гораздо полезнее.


  13. Деьмянчук Виталий :

    Спасибо за оригинальное решение поставленной задачи.


  14. Серж :

    Спасиб :)


  15. Chup :

    Надо же, даже не подумал бы что такое бывает :) Спасибо!


  16. 16 действительно полезных решений для JavaScript « шаманские бредни :

    [...] 8. Набор функций, позволяющих получить координаты элемента на экране пользователя. Если ваш документ статичен относительно окна и не имеет скроллбаров – лучше использовать функцию getPosition – так будет быстрее. В обратном случае используйте getAlignedPosition – она учитывает положения скроллбаров. Только обратите внимание: значение top у элемента может быть орицательным, если элемент верхней частью за пределами окна – для синхронизации с курсором мыши иногда нужно обнулить в этом случае высоту. Основной скрипт позаимствован из одного блога, Aligned-версия – результат поисков по сусекам и совмещения с информацией из двух статей (при обнаружении DOCTYPE IE входит в свой собственный, несколько непредсказуемый, режим). [...]


  17. Станислав :

    Скажите, а есть ли какая то информация о том, по какому принципу сжимает картинки движок dle? Не очень нравится результат его работы – почему то сильно “замыливаются” картинки. http://www.skuterist.ru/uploads/posts/1197489721_01.jpg
    Обратите внимание на цифры (дату фотографии). На оригинале они четкие, здесь – как будто смазаны.
    Можно ли как то переписыть модель ужимки фотографии в дле?


  18. Самара :

    +1, схожие мысли…


  19. Atner :

    2 Welder,
    сайт по указанному адресу не существует :(
    А взглянуть на творчество ой как шибко хочется.


  20. Александр :

    К сожаление, вторая версия скрипта не доступна. Возможно автор обновит…



22 queries 0.212 seconds.