Używam elementów kanwy html5 do zmiany rozmiaru obrazów w mojej przeglądarce. Okazuje się, że jakość jest bardzo niska. Znalazłem to: Wyłącz interpolację podczas skalowania <canvas>, ale nie pomaga to w poprawie jakości.
Poniżej znajduje się mój kod css i js, a także obraz wyskalowany w Photoshopie i skalowany w API Canvas.
Co muszę zrobić, aby uzyskać optymalną jakość podczas skalowania obrazu w przeglądarce?
Uwaga: chcę zmniejszyć duży obraz do małego, zmodyfikować kolor na płótnie i wysłać wynik z płótna na serwer.
CSS:
canvas, img {
image-rendering: optimizeQuality;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
JS:
var $img = $('<img>');
var $originalCanvas = $('<canvas>');
$img.load(function() {
var originalContext = $originalCanvas[0].getContext('2d');
originalContext.imageSmoothingEnabled = false;
originalContext.webkitImageSmoothingEnabled = false;
originalContext.mozImageSmoothingEnabled = false;
originalContext.drawImage(this, 0, 0, 379, 500);
});
Obraz zmieniony w Photoshopie:
Obraz zmieniony na płótnie:
Edytować:
Próbowałem zmniejszyć skalowanie w więcej niż jednym kroku, jak zaproponowano w:
Zmiana rozmiaru obrazu na kanwie HTML5 i drawImage w formacie HTML5 : jak zastosować antyaliasing
Oto funkcja, której użyłem:
function resizeCanvasImage(img, canvas, maxWidth, maxHeight) {
var imgWidth = img.width,
imgHeight = img.height;
var ratio = 1, ratio1 = 1, ratio2 = 1;
ratio1 = maxWidth / imgWidth;
ratio2 = maxHeight / imgHeight;
// Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
if (ratio1 < ratio2) {
ratio = ratio1;
}
else {
ratio = ratio2;
}
var canvasContext = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
var canvasCopy2 = document.createElement("canvas");
var copyContext2 = canvasCopy2.getContext("2d");
canvasCopy.width = imgWidth;
canvasCopy.height = imgHeight;
copyContext.drawImage(img, 0, 0);
// init
canvasCopy2.width = imgWidth;
canvasCopy2.height = imgHeight;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
var rounds = 2;
var roundRatio = ratio * rounds;
for (var i = 1; i <= rounds; i++) {
console.log("Step: "+i);
// tmp
canvasCopy.width = imgWidth * roundRatio / i;
canvasCopy.height = imgHeight * roundRatio / i;
copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);
// copy back
canvasCopy2.width = imgWidth * roundRatio / i;
canvasCopy2.height = imgHeight * roundRatio / i;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
} // end for
// copy back to canvas
canvas.width = imgWidth * roundRatio / rounds;
canvas.height = imgHeight * roundRatio / rounds;
canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);
}
Oto wynik, jeśli użyję rozmiaru 2 stopni w dół:
Oto wynik, jeśli użyję rozmiaru 3 stopni w dół:
Oto wynik, jeśli użyję rozmiaru 4 stopniowego:
Oto wynik, jeśli użyję rozmiaru o 20 stopni:
Uwaga: Okazuje się, że od 1 do 2 kroków następuje duża poprawa jakości obrazu, ale im więcej kroków dodasz do procesu, tym bardziej rozmyty staje się obraz.
Czy istnieje sposób na rozwiązanie problemu polegającego na tym, że obraz staje się bardziej rozmyty, im więcej kroków dodajesz?
Edytuj 2013-10-04: Wypróbowałem algorytm GameAlchemist. Oto wynik w porównaniu do Photoshopa.
Zdjęcie PhotoShop:
Algorytm GameAlchemist:
źródło
Odpowiedzi:
Ponieważ Twoim problemem jest zmniejszenie skali obrazu, nie ma sensu mówić o interpolacji - czyli o tworzeniu pikseli -. Problem polega na tym, że próbkowanie spada.
Aby zmniejszyć próbkowanie obrazu, musimy zamienić każdy kwadrat p * p pikseli w oryginalnym obrazie na pojedynczy piksel w obrazie docelowym.
Ze względu na wydajność przeglądarki wykonują bardzo proste próbkowanie w dół: aby zbudować mniejszy obraz, wybierają po prostu JEDEN piksel w źródle i używają jego wartości jako miejsca docelowego. który „zapomina” o niektórych szczegółach i dodaje szumu.
Jest jednak wyjątek od tej reguły: ponieważ próbkowanie obrazu 2X jest bardzo proste do obliczenia (średnio 4 piksele, aby jeden) i jest używane do pikseli siatkówki / HiDPI, ten przypadek jest obsługiwany prawidłowo - przeglądarka wykorzystuje 4 piksele, aby uzyskać jeden-.
ALE ... jeśli użyjesz kilkukrotnie downsamplingu 2X, napotkasz problem, że kolejne błędy zaokrąglania będą powodować zbyt duży szum.
Co gorsza, nie zawsze zmieniasz rozmiar o potęgę dwóch, a zmiana rozmiaru do najbliższej potęgi + ostatnia zmiana rozmiaru jest bardzo głośna.
To, czego szukasz, to próbkowanie w dół z dokładnością do piksela, czyli ponowne próbkowanie obrazu, które uwzględni wszystkie piksele wejściowe - niezależnie od skali -.
Aby to zrobić, musimy obliczyć, dla każdego piksela wejściowego, jego udział w jednym, dwóch lub czterech pikselach docelowych, w zależności od tego, czy skalowana projekcja pikseli wejściowych znajduje się bezpośrednio w pikselach docelowych, zachodzi na obramowanie X, obramowanie Y, czy oba .
(Schemat byłby tutaj fajny, ale ja go nie mam).
Oto przykład skali płótna w porównaniu z moją idealną skalą piksela w skali 1/3 zombata.
Zauważ, że obraz może zostać przeskalowany w Twojej przeglądarce i jest .jpegized przez SO.
Widzimy jednak, że jest znacznie mniej hałasu, zwłaszcza w trawie za wombatem i gałęziach po jego prawej stronie. Hałas w futrze sprawia, że jest bardziej kontrastowy, ale wygląda na to, że ma białe włosy - w przeciwieństwie do zdjęcia źródłowego -.
Właściwy obraz jest mniej chwytliwy, ale zdecydowanie ładniejszy.
Oto kod do wykonania idealnego skalowania w dół:
fiddle wynik: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
fiddle: http://jsfiddle.net/gamealchemist/r6aVp/
Jest dość chciwy na pamięć, ponieważ do przechowywania pośrednich wartości obrazu docelowego potrzebny jest bufor typu float (-> jeśli policzymy płótno wyników, w tym algorytmie wykorzystamy 6 razy pamięć obrazu źródłowego).
Jest to również dość drogie, ponieważ każdy piksel źródłowy jest używany niezależnie od rozmiaru docelowego, a my musimy zapłacić za getImageData / putImageDate, również dość powolny.
Ale w tym przypadku nie ma sposobu, aby być szybszym niż przetwarzanie każdej wartości źródłowej, a sytuacja nie jest taka zła: w przypadku mojego obrazu wombata 740 * 556 przetwarzanie trwa od 30 do 40 ms.
źródło
Szybkie ponowne próbkowanie płótna z dobrą jakością: http://jsfiddle.net/9g9Nv/442/
Aktualizacja: wersja 2.0 (szybsza, pracownicy sieciowi + obiekty do przenoszenia) - https://github.com/viliusle/Hermite-resize
źródło
Sugestia 1 - przedłużyć rurociąg technologiczny
Możesz użyć step-down, jak opisuję w linkach, do których się odnosisz, ale wydaje się, że używasz ich w niewłaściwy sposób.
Zmniejszenie nie jest konieczne, aby skalować obrazy do proporcji powyżej 1: 2 (zazwyczaj, ale nie tylko). To jest miejsce, w którym musisz zrobić plik drastycznego skalowania w dół, musisz podzielić go na dwa (a rzadko więcej) etapy w zależności od zawartości obrazu (w szczególności tam, gdzie występują wysokie częstotliwości, takie jak cienkie linie).
Za każdym razem, gdy próbujesz pobrać obraz, stracisz szczegóły i informacje. Nie można oczekiwać, że wynikowy obraz będzie tak wyraźny jak oryginał.
Jeśli następnie pomniejszasz obrazy w wielu krokach, w sumie stracisz wiele informacji, a wynik będzie kiepski, jak już zauważyłeś.
Spróbuj z jednym dodatkowym krokiem lub dwoma dodatkowymi krokami.
Zwoje
W przypadku Photoshopa zauważ, że stosuje on splot po ponownym próbkowaniu obrazu, na przykład wyostrzenie. Nie chodzi tylko o interpolację bi-sześcienną, więc aby w pełni emulować Photoshopa, musimy również dodać kroki, które wykonuje Photoshop (przy domyślnej konfiguracji).
W tym przykładzie użyję mojej oryginalnej odpowiedzi, do której odwołujesz się w swoim poście, ale dodałem do niej splot wyostrzający, aby poprawić jakość jako proces postu (patrz demo na dole).
Oto kod dodawania filtra wyostrzającego (jest oparty na ogólnym filtrze splotowym - w nim umieściłem macierz wag do wyostrzania, a także współczynnik mieszania, aby dostosować wymowę efektu):
Stosowanie:
Plik
mixFactor
Ma wartość pomiędzy [0.0, 1.0] i pozwala zrobić bagatelizować efektu wyostrzania - praworządności, zasada: im mniej tym mniejsza wielkość efektu jest potrzebny.Funkcja (na podstawie tego fragmentu ):
Rezultatem zastosowania tej kombinacji będzie:
DEMO ONLINE TUTAJ
W zależności od tego, ile wyostrzenia chcesz dodać do mieszanki, możesz uzyskać efekt od domyślnego „rozmytego” do bardzo ostrego:
Sugestia 2 - implementacja algorytmu niskiego poziomu
Jeśli chcesz uzyskać najlepszy wynik pod względem jakości, musisz zejść na niższy poziom i rozważyć wdrożenie na przykład tego zupełnie nowego algorytmu, aby to zrobić.
Zobacz próbkowanie obrazu zależne od interpolacji (2011) z IEEE.
Tutaj jest link do pełnego artykułu (PDF) .
W tej chwili nie ma implementacji tego algorytmu w JavaScript AFAIK, więc jeśli chcesz rzucić się w wir tego zadania, możesz mieć wiele do zrobienia.
Istota to (fragmenty artykułu):
Abstrakcyjny
(zobacz podany link, aby uzyskać wszystkie szczegóły, formuły itp.)
źródło
Jeśli chcesz używać tylko płótna, najlepszym rezultatem będzie kilka kroków w dół. Ale to jeszcze nie wystarczy. Aby uzyskać lepszą jakość, potrzebujesz czystej implementacji js. Właśnie wydaliśmy pica - szybki downscaler ze zmienną jakością / prędkością. Krótko mówiąc, zmienia rozmiar 1280 * 1024 pikseli w ~ 0,1 s i obrazu 5000 * 3000 pikseli w 1 s, z najwyższą jakością (filtr lanczos z 3 płatami). Pica ma wersję demonstracyjną , w której możesz bawić się swoimi obrazami, poziomami jakości, a nawet wypróbować na urządzeniach mobilnych.
Pica nie ma jeszcze nieostrej maski, ale zostanie ona dodana wkrótce. To znacznie łatwiejsze niż zaimplementowanie filtra splotu o dużej szybkości w celu zmiany rozmiaru.
źródło
Po co używać płótna do zmiany rozmiaru obrazów? Wszystkie współczesne przeglądarki używają interpolacji bicubic - tego samego procesu, którego używa Photoshop (jeśli robisz to dobrze) - i robią to szybciej niż proces płótna. Po prostu określ żądany rozmiar obrazu (użyj tylko jednego wymiaru, wysokości lub szerokości, aby zmienić rozmiar proporcjonalnie).
Jest to obsługiwane przez większość przeglądarek, w tym nowsze wersje IE. Wcześniejsze wersje mogą wymagać CSS dla konkretnej przeglądarki .
Prosta funkcja (wykorzystująca jQuery) do zmiany rozmiaru obrazu wyglądałaby następująco:
Następnie użyj zwróconej wartości, aby zmienić rozmiar obrazu w jednym lub obu wymiarach.
Oczywiście są różne udoskonalenia, które możesz wprowadzić, ale to spełnia swoje zadanie.
Wklej następujący kod do konsoli tej strony i obserwuj, co stanie się z gravatarami:
źródło
Nie jest to dobra odpowiedź dla osób, które naprawdę potrzebują zmienić rozmiar samego obrazu, ale po prostu zmniejszyć rozmiar pliku .
Miałem problem ze zdjęciami „prosto z aparatu”, które moi klienci często przesyłali w „nieskompresowanym” formacie JPEG.
Nie tak dobrze wiadomo, że płótno obsługuje (w większości przeglądarek 2017) zmianę jakości JPEG
Dzięki tej sztuczce mógłbym zmniejszyć zdjęcia 4k x 3k z> 10Mb do 1 lub 2Mb, z pewnością zależy to od twoich potrzeb.
Popatrz tutaj
źródło
Oto usługa Angular wielokrotnego użytku do zmiany rozmiaru obrazu / płótna w wysokiej jakości: https://gist.github.com/fisch0920/37bac5e741eaec60e983
Usługa obsługuje splot lanczosa i stopniowe zmniejszanie skali. Podejście splotowe zapewnia wyższą jakość kosztem wolniejszego działania, podczas gdy podejście stopniowego zmniejszania skali daje rozsądnie antyaliasowane wyniki i jest znacznie szybsze.
Przykładowe użycie:
źródło
To jest ulepszony filtr zmiany rozmiaru Hermite, który wykorzystuje 1 pracownika, dzięki czemu okno nie zamarza.
https://github.com/calvintwr/blitz-hermite-resize
źródło
Znalazłem rozwiązanie, które nie wymaga bezpośredniego dostępu do danych pikseli i ich zapętlania, aby wykonać downsampling. W zależności od rozmiaru obrazu może to wymagać dużych zasobów i lepiej byłoby użyć wewnętrznych algorytmów przeglądarki.
Funkcja drawImage () wykorzystuje metodę ponownego próbkowania z interpolacją liniową i metodą najbliższego sąsiada. Że działa dobrze, gdy nie jest zmiana rozmiaru w dół więcej niż połowę pierwotnego rozmiaru .
Jeśli zrobisz pętlę, aby zmienić rozmiar tylko maksymalnie o połowę na raz, wyniki byłyby całkiem dobre i znacznie szybsze niż dostęp do danych pikseli.
Ta funkcja zmniejsza próbkowanie o połowę, aż do osiągnięcia pożądanego rozmiaru:
źródło
Może ty możesz spróbować tego, co zawsze używam w swoim projekcie, dzięki czemu możesz nie tylko uzyskać wysokiej jakości obraz, ale każdy inny element na swoim płótnie.
źródło
zamiast 0,85 , jeśli dodamy 1,0 . Otrzymasz dokładną odpowiedź.
Możesz uzyskać wyraźny i jasny obraz. Proszę sprawdzić
źródło
Naprawdę staram się unikać przeglądania danych obrazu, szczególnie w przypadku większych obrazów. W ten sposób wymyśliłem dość prosty sposób na przyzwoite zmniejszenie rozmiaru obrazu bez żadnych ograniczeń i ograniczeń za pomocą kilku dodatkowych kroków. Ta procedura sprowadza się do najniższego możliwego pół kroku przed pożądanym rozmiarem docelowym. Następnie skaluje go do dwukrotności rozmiaru docelowego, a następnie ponownie o połowę. Na początku brzmi śmiesznie, ale wyniki są zdumiewająco dobre i szybko się udają.
źródło
context.scale(xScale, yScale)
źródło
DEMO : Zmiana rozmiaru obrazów za pomocą skrzynek JS i HTML Canvas Demo.
Możesz znaleźć 3 różne metody zmiany rozmiaru, które pomogą ci zrozumieć, jak działa kod i dlaczego.
https://jsfiddle.net/1b68eLdr/93089/
Pełny kod zarówno demonstracji, jak i metody TypeScript, której możesz chcieć użyć w swoim kodzie, można znaleźć w projekcie GitHub.
https://github.com/eyalc4/ts-image-resizer
To jest ostateczny kod:
źródło