Node.js: zmiana rozmiaru obrazu bez ImageMagick

85

Tworzę aplikację internetową na Node.js (+ express 4), w której użytkownicy mogą ustawić swój obraz profilu, przesyłając go na serwer. Już ograniczamy typ MIME pliku i maksymalny rozmiar pliku, więc użytkownik nie może przesłać więcej niż 200 KB obrazów png lub jpeg.

Problem polega na tym, że chcielibyśmy zmienić rozmiar (po stronie serwera) przesyłanego obrazu do rozdzielczości 200x200, aby poprawić ładowanie strony i zaoszczędzić miejsce na dysku. Po kilku badaniach wszystkie odpowiedzi wskazywały na użycie dowolnego modułu opartego na ImageMagick lub GraphicsMagick.

Jednak konieczność zainstalowania ImageMagick / GraphicsMagick, aby wykonać prostą zmianę rozmiaru obrazu, wydaje mi się zbyt przesadą, więc czy istnieje inne rozwiązanie niż to dla Node.js?

Edycja: Zmieniłem zaakceptowane rozwiązanie na ostre, ponieważ poprzednie rozwiązanie (lwip) nie jest już obsługiwane. Dziękujemy za Twoją opinię!

zacr0
źródło
Cześć. Mam pytanie. Jak zmniejszyć rozmiar obrazu poniżej 200 KB? Proszę, wyjaśnij drogę. Dzięki.
C.Petrescu
Witaj, to pytanie jest warte opublikowania jako nowego, jeśli nie znajdziesz żadnego pokrewnego, które zostało wcześniej opublikowane. Aby dać ci trochę światła, spróbuj poszukać metod kompresji i zmiany rozmiaru w API dostarczonym dla narzędzi, które możesz znaleźć w tym pytaniu.
zacr0

Odpowiedzi:

92

Głosowałbym na ostry :

sharp('input.jpg')
  .resize(200, 200)
  .toFile('ouput.jpg', function(err) {
    // output.jpg is a 200 pixels wide and 200 pixels high image
    // containing a scaled and cropped version of input.jpg
  });

Jest szybki, zwykle 6x szybszy niż najszybsze wiązania węzłów oparte na obrazie i działa przy bardzo małej ilości pamięci, być może 10x mniej . ostre linki do biblioteki obrazów libvips bezpośrednio, nie ma wyrzucania do zewnętrznego programu, a sama biblioteka jest szybsza i bardziej wydajna niż * magia w tym zadaniu. Obsługuje przydatne rzeczy, takie jak strumień, bufor i wejście i wyjście systemu plików, zarządzanie kolorami, przezroczystość, obietnice, nakładki, WebP, SVG i inne.

Od wersji Sharp 0.20 npm będzie automatycznie pobierać kompletne, wstępnie skompilowane pliki binarne na większości platform, więc nie ma potrzeby korzystania z node-gyp. Wystarczy wpisać:

npm install sharp

lub:

yarn add sharp

I ruszaj.

jcupitt
źródło
7
Od wersji 0.12.0 sharpnie ma już żadnych zewnętrznych zależności wykonawczych dla użytkowników Linuksa i Windows, ponieważ zawiera wstępnie skompilowaną wersję libvips. Przekonasz się, że operacje zmiany rozmiaru są ~ 10x szybsze niż LWIP i przy ułamku użycia pamięci.
Lovell Fuller
4
ostre dodane natywne wsparcie dla gif i svg w wersji 0.15 sharp.dimens.io/en/stable/changelog
jcupitt
1
To setki plików, ale wszystkie są zarządzane automatycznie przez npm, nie musisz o tym myśleć.
jcupitt
4
@CoDEmanX Być może spróbuj npm install --global --production windows-build-toolsnajpierw uruchomić . Zobacz także github.com/Microsoft/nodejs-guidelines/blob/master/…
Lovell Fuller
1
Zbudowałem mały skrypt, który ułatwia zobaczenie, co się dzieje i jak będzie wyglądać w treści, takiej jak karty boostrap i tło: okładka, aby najlepiej zoptymalizować parametry, może to intrest github.com/lcherone/sharp-test
Lawrence Cherone
71

Niedawno zacząłem tworzyć moduł przetwarzania obrazu dla NodeJS bez żadnych zależności w czasie wykonywania ( przeczytaj dlaczego ). Jest wciąż na wczesnym etapie, ale już można go używać.

To, o co prosisz, powinno wyglądać następująco:

image.resize(200, 200, function(err, image){
    // encode resized image to jpeg and get a Buffer object
    image.toBuffer('jpg', function(err, buffer){
        // save buffer to disk / send over network / etc.
    });
});

Więcej informacji w repozytorium Github modułu .

EyalAr
źródło
7
Twój moduł jest niesamowity. Jednak zajmuje to dużo pamięci. Próbowałem writeFile uzyskać 1.8Mbobraz i wymaga 130 MB pamięci. Następnie 4MB, 11000x6000wykonuję test, zmieniając rozmiar obrazu do kilku miniatur (640,560,480, ..., 160) i zajmuje około 1,7 GB pamięci. Czy to błąd?
Lewis
2
Cześć @Orion, dzięki za informację zwrotną. Przejdź do repozytorium Github i otwórz problem, podając więcej szczegółów (system operacyjny, wersje, kod do odtworzenia). Spróbujemy razem rozwiązać ten problem :)
EyalAr
@EyalAr Hej Eyal, jak zmienić rozmiar i zachować proporcje? (zmień rozmiar na maksymalny możliwy rozmiar szerokości, wysokości) bez rozciągania
Daniel Krom
10
Sugerowałbym nie używać lwip w 2017 roku. Pakiet wydaje się nie być już obsługiwany i ma ogromne problemy z instalacją w systemie Windows, a teraz nawet na platformach Unix.
zerefel
9
lwip to niestety martwy projekt. ostry, jednak nadal wydaje się być aktywnie utrzymywany.
laurent
16

Spójrz na lwip: https://github.com/EyalAr/lwip

Bardzo prosty i łatwy w użyciu

npm install lwip

a następnie w kodzie węzła,

// obtain an image object:
require('lwip').open('image.jpg', function(err, image){

  // check err...
  // define a batch of manipulations and save to disk as JPEG:
  image.batch()
    .scale(0.75)          // scale to 75%
    .rotate(45, 'white')  // rotate 45degs clockwise (white fill)
    .crop(200)            // crop a 200X200 square from center
    .blur(5)              // Gaussian blur with SD=5
    .writeFile('output.jpg', function(err){
      // check err...
      // done.
    });

});

Z powodzeniem zaimplementowałem to w moim programie do przesyłania plików i działa to jak urok.

Arvind
źródło
1
To jest to, czego szukałem, bez konieczności instalowania przesadnych, ciężkich zależności zewnętrznych dla kilku funkcji związanych ze zmianą rozmiaru obrazu.
zacr0
3
Btw @EyalAr jest autorem tego modułu node. Jego komentarz jest również wymieniony poniżej.
Arvind
Bardzo intuicyjny w instalacji i obsłudze. Bardzo podoba mi się to, że nie musisz implementować żadnych bibliotek binarnych, takich jak ImageMagick.
ChrisRich
To dokładnie to samo, co ta odpowiedź (właściciel lwip), ale później: stackoverflow.com/a/24543924/1525495
Jorge Fuentes González
12

Istnieje dobra biblioteka do manipulacji obrazami, napisana w całości w JavaScript, bez zależności od innych bibliotek, Jimp. https://github.com/oliver-moran/jimp

Przykładowe użycie:

var Jimp = require("jimp");

// open a file called "lenna.png"
Jimp.read("lenna.png", function (err, lenna) {
    if (err) throw err;
    lenna.resize(256, 256)            // resize
         .quality(60)                 // set JPEG quality
         .write("lena-small.jpg"); // save
});
edtech
źródło
Jak szybko to jest w porównaniu do ImageMagik?
Christopher Grigg,
2
Sharp ma zestaw wzorców: sharp.dimens.io/en/stable/performance --- W tym teście jimp jest 5x wolniejszy niż IM i 30x wolniejszy niż ostry. Oczywiście wystarczająco szybki jest wystarczająco szybki, a prędkość nie jest jedynym czynnikiem do rozważenia.
jcupitt
Może nie w czasie rzeczywistym, ale Jimp jest świetny do pisania po prostu plików miniatur o wielu rozmiarach (i później odzyskuje je jako pliki z pamięci podręcznej).
coderofsalvation
jimp nie obsługuje webp i nie będzie obsługiwał w najbliższej przyszłości. Zobacz: github.com/oliver-moran/jimp/issues/144
Viacheslav Dobromyslov
8

Sharp cieszy się ostatnio dużą popularnością, ale jest to ten sam pomysł, co wiązania * Magick.

Jednak konieczność zainstalowania programu ImageMagick / GraphicsMagick, aby wykonać prostą zmianę rozmiaru obrazu, wydaje mi się zbyt przesadna

Zmiana rozmiaru obrazu nie jest prosta. Format JPEG jest szczególnie złożony i istnieje kilka sposobów skalowania grafiki w celu uzyskania wyników o różnej jakości. Niewiele z nich można łatwo zaimplementować. Istnieją biblioteki przetwarzające obrazy, które wykonują to zadanie, więc jeśli nie ma innego powodu, dla którego nie możesz ich zainstalować, zrób to.

Ry-
źródło
13
Może jestem leniwym programistą, ale gdy tylko zobaczyłem proces instalacji ImageMagick i zastanawiałem się, ile wydałbym na zainstalowanie go na mojej instancji Amazon AWS EC2, od razu zacząłem szukać innych opcji - zwłaszcza biorąc pod uwagę, że wszystko, czego potrzebowałem, to możliwość zmiany rozmiaru obrazów dla miniatur.
ChrisRich
7

Canvas jest 2,3 razy szybsze niż ImageMagic.

Możesz spróbować porównać moduły Node.js do manipulacji obrazami - https://github.com/ivanoff/images-manipulation-performance

author's results:
 sharp.js : 9.501 img/sec; minFreeMem: 929Mb
 canvas.js : 8.246 img/sec; minFreeMem: 578Mb
 gm.js : 4.433 img/sec; minFreeMem: 791Mb
 gm-imagemagic.js : 3.654 img/sec; minFreeMem: 804Mb
 lwip.js : 1.203 img/sec; minFreeMem: 54Mb
 jimp.js : 0.445 img/sec; minFreeMem: 82Mb
Dimitry Ivanov
źródło
3

Jeśli nie potrzebujesz dużego obrazu, możesz zmienić jego rozmiar po stronie klienta przed przesłaniem:

Odczytywanie plików w języku JavaScript przy użyciu interfejsów API plików

Obraz zmieniający rozmiar po stronie klienta za pomocą javascript przed przesłaniem na serwer

Wielu użytkowników może mieć dobry obraz siebie ze smartfona, a wielu z nich ma ponad 200 kB. Należy pamiętać, że dane dostarczone przez klienta nie są zaufane, więc sprawdzanie po stronie serwera nadal ma zastosowanie.

Andrei Volgin
źródło
2
Nigdy nie możesz ufać klientowi, użytkownik musi tylko znać punkt końcowy przesyłania, aby wysłać tam, co chce. Tak więc walidacje takie jak rozmiar pliku nadal mają zastosowanie. Jednak zmiana rozmiaru po stronie klienta jest dobrym pomysłem.
Kev
1

Używałem lwip (jak sugerował wcześniej arvind), ale przełączyłem się na png-crop . Wydaje mi się, że działa trochę szybciej (Win 8.1 x64, Node v0.12.7). Kod w repozytorium wygląda na niewiarygodnie lekki, a operacyjnie jest prosty w użyciu.

var pngcrop = require('png-crop');
var config = {left: 10, top: 100, height: 150, width: 150};
pngcrop.crop('cats.png','cats-cropped.png',config);

Oczywiście będzie to robić tylko pliki png ...

Dan Caseley
źródło
0

Sharp działa bardzo dobrze i jest łatwy w użyciu ze strumieniami, działa jak urok, ale musisz go skompilować z wersją węzła, jest to wada. Używałem Sharp do przetwarzania obrazu, z obrazem z wiadra AWS S3 i działałem idealnie, ale musiałem użyć innego modułu. GM nie działał dla mnie, ale Jimp pracował bardzo dobrze!

Musisz zwrócić uwagę na ścieżkę zapisanego obrazu, może to spowodować błędy, jeśli zaczniesz ścieżkę od znaku „/”.

Oto jak użyłem Jimp w nodeJS:

const imageUrl = `SOME_URL`;
let imgExported = 'EXPORTED_PIC.png';

Jimp.read(imageUrl)
    .then(image => {
        image   
            .resize(X, Y) 
            .write(`tmp/`+ imgExported, err => { 
                if(err) 
                    console.error('Write error: ', err);
                else { ... // don't forget to put a callback() } }

            });

Uważaj również na kolejność wykonywania, umieść callback, aby inne rzeczy nie wydarzyły się, gdy nie chcesz. Próbowałem użyć "await" dla Jimp.read (), ale to nie działa dobrze.

Alex Seceleanu
źródło
Od ostrego 0.20 automatycznie pobierze wstępnie skompilowany plik binarny dla dokładnej wersji węzła na większości platform, więc nie ma potrzeby niczego budować.
jcupitt
Niestety to nie zadziałało. Musiałem użyć Sharp w systemie plików tylko do odczytu, z różnymi wersjami node.js i musiałem pobrać moduł Sharp dla każdej używanej wersji węzła i zajmowało to zbyt dużo czasu.
Alex Seceleanu
0

Możesz to zrobić za pomocą jimp (node_module)

Zapis lokalny:

Jimp.read(path) // this can be url or local location
      .then(image=> {
          image
            .resize(size, Jimp.AUTO) // jimp.AUTO automatically sets the width so that the image doesnot looks odd
            .write('path-to-save');
      })
      .catch(err => {
        console.log(err);
      });

Aby przesłać do s3 lub gdziekolwiek chcesz.

Jimp.read(urls) // this can be url or local location
          .then(image=> {
              image
                .resize(size, Jimp.AUTO) // jimp.AUTO automatically sets the width so that the image doesnot looks odd
                .getBase64(Jimp.AUTO, (err, res) => {
                  const buf = new Buffer(
                    res.replace(/^data:image\/\w+;base64,/, ""),
                    "base64"
                  );
                  var data = {
                    Key: key,
                    Bucket: bucket,
                    Body: body,
                    ContentEncoding: "base64",
                    ContentType: "image/jpeg"
                  };
                  s3.putObject(data, function(err, data) {
                    if (err) {
                      throw err;
                    } else {
                      console.log("succesfully uploaded the image!");
                    }
                  });
                });
          })
          .catch(err => {
            console.log(err);
          });
Nouman Dilshad
źródło
0

Lubię bibliotekę resize-img ze względu na jej prostotę.

const fs = require('fs');
const resizeImg = require('resize-img');

(async () => {
    const image = fs.readFileSync('unicorn.png');

    const newImage = await resizeImg(image, { width: 128, height: 128 });

    fs.writeFileSync('unicorn-128x128.png', newImage);
})();
ns16
źródło
0

Zaimplementowano zmianę rozmiaru obrazu za pomocą Google Drive API v3 . Ta metoda jest zalecana w przypadku Google Apps Script do wstawiania obrazów do Arkuszy Google.

Algorytm:

  1. Prześlij obraz do folderu Dysku Google.
  2. Uzyskaj publiczny adres URL miniatury obrazu.
  3. Zastąp parametr „resize” w adresie URL wymaganą szerokością i / lub wysokością. (Domyślny rozmiar miniatury to 220px).
  4. Pobierz miniaturę o zmienionym rozmiarze z Dysku Google.

Zobacz przykład tutaj: https://github.com/dobromyslov/google-drive-utils/blob/511c44c2c48862b47c60038423b7f71bf1d28f49/src/index.ts#L150

I uważaj na limity GDrive:

  • zapytań dziennie: 1000000000
  • zapytań na 100 sekund na użytkownika: 1000
  • zapytań na 100 sekund: 10000
Viacheslav Dobromyslov
źródło