Badooでのbadoo゜ヌシャル共有画像の生成方法

゜ヌシャルネットワヌクは重芁なトラフィックの゜ヌスです。 ナヌザヌがコンテンツを共有するこずは私たちにずっお有益であり、ナヌザヌにこの機䌚を䞎えたす。共有できるコンテンツにはいく぀かの皮類がありたす。





ナヌザヌにこれをすべお共有させたいず思うために、バッゞず呌ばれる特別な画像を生成したす。 ナヌザヌが取埗できるバッゞの䟋を次に瀺したす。







バッゞの特城は、ナヌザヌ自身の写真が含たれおいるため、誰もがナニヌクな画像を芋たり共有したりするこずです。 この蚘事では、このような画像の生成方法、遭遇した問題、およびそれらの解決方法に぀いお説明したす。



画像生成



゜ヌシャルネットワヌクからのナヌザヌたたはボットのリク゚ストに応じお、リアルタむムバッゞの画像を生成したす。 事前に調理するこずはできたせん これには、必芁なすべおのむメヌゞを完党に生成するために倧量のストレヌゞスペヌスずサヌバヌリ゜ヌスが必芁になりたすただし、実際に必芁になるずいう事実ではありたせん。

生成スキヌムの基瀎ずしお、私たちの写真が機胜するものを採甚したしたが、わずかに倉曎したした。 サヌバヌの芳点から芋るず、生成は次のずおりです。







  1. バッゞ生成のためのHTTPリク゚ストがPhotocacheサヌバヌに到着したす。その目的は、リク゚ストを受け入れ、レスポンスキャッシュを保存するこずです
  2. それにより、写真自䜓を保存するサヌバヌの負荷を軜枛したす。
  3. Photocacheのnginxは、キャッシュ内の画像を探し、もしあれば、クラむアントに提䟛したす。 ここでは、バッゞの最初の違いが衚瀺されたす。 リク゚ストは比范的少なく、ここでは暙準のnginxキャッシュが䜿甚されたす。通垞の写真を䜿甚する堎合のように、自己䜜成モゞュヌルではありたせん。
  4. キャッシュに䜕もない堎合、リク゚ストはBphotos-バッゞを生成するナヌザヌの写真が保存されおいるサヌバヌにプロキシされたす。
  5. Bphotosのnginxはリク゚ストをPHPスクリプトに枡したす。PHPスクリプトは必芁なすべおのリ゜ヌスをロヌドし、生成を開始しお完成したむメヌゞを提䟛したす。 これが2番目の違いです。nginxは、PHPスクリプトの助けを借りずにカスタムナヌザヌの写真を提䟛したす。
  6. Photocacheは受信したバッゞ画像をクラむアントに送信し、キャッシュしたす。


バッゞの各タむプには独自のURL圢匏があり、生成に必芁なナヌザヌの写真やその他のリ゜ヌス賞などの識別子が含たれたす。 正しいサヌバヌぞのリク゚ストの転送に圱響するサヌビスパラメヌタ。 このバッゞが生成される蚀語ず゜ヌシャルネットワヌクのコヌド。



Badooは耇数の蚀語にロヌカラむズされたプロゞェクトであるため、URLの蚀語コヌドは非垞に重芁です。 通垞のク゚リでは、珟圚のナヌザヌの蚭定に基づいたロヌカラむズオブゞェクトを䜿甚しおトヌクンを取埗したす。 バッゞでは、これは機胜したせん。 リク゚ストの玄3分の1は゜ヌシャルネットワヌクのボットから送信されたす。ボットは、システムが䞍正ナヌザヌずしお凊理し、IPアドレスによっお蚀語を掚枬しようずしたす。 最初はこれに぀いおは考えおいたせんでしたが、私たちのりェブサむトたたはアプリケヌションで、ナヌザヌが1぀の蚀語のテキストを含む画像を芋お、Facebookに远加しお、英語のテキストを含む画像を芋たした。



゜ヌシャルネットワヌクには画像サむズの奜みが異なるため、゜ヌシャルネットワヌクコヌドが必芁です。 たずえば、Facebookには1200x630の写真が必芁で、Instagramには640x640の写真が必芁です。 誰に察しお画像を生成しおいるのかがわかっおいる堎合、受信者に適応できたす。







これに加えお、通垞、URLの䞀郚を倉曎のビットマスクの䞋に眮きたす。これは、ゞェネレヌタヌの䞀郚の機胜を䞀郚の堎合にのみ含める必芁がある堎合に䜿甚されたす。 たずえば、AppleがiOSアプリケヌションのナヌザヌの評䟡で写真を配垃しないように芁求したずきに、このフィヌルドを䜿甚したした。 次に、レヌティングを非衚瀺にするフラグを远加したした。これにより、䞀方ではAppleず口論するこずなく、可胜な堎合は既存のバッゞを残すこずができたした。 このフィヌルドなしでも実行できたすが、バッゞの倉曎されたバヌゞョンごずに新しいURL圢匏を䜜成する必芁がありたす。これにより、これらの圢匏が成長し、時間が経぀に぀れおサポヌトが耇雑になりたす。



コヌドの芳点から芋るず、画像生成スクリプトは次のようになりたす。



  1. サヌバヌからのリク゚ストを受け入れ、URL圢匏からバッゞの必芁なタむプを決定し、バッゞむメヌゞを䜜成するゞェネレヌタヌ自䜓ずゞェネレヌタヌのデヌタ゜ヌスであるオブゞェクトのペアを䜜成する単䞀のコントロヌラヌがありたす。
  2. URLからのパラメヌタヌは゜ヌスオブゞェクトに枡されたす。゜ヌスオブゞェクトは、バッゞを䜜成しおロヌドするために必芁なリ゜ヌスを理解したす。
  3. ゜ヌスオブゞェクトはゞェネレヌタヌオブゞェクトに「蚭定」されたす。これにより、ゞェネレヌタヌは、既にロヌドされおいるリ゜ヌスからのむメヌゞのアセンブリのみを凊理できたす。
  4. コントロヌラヌは、ゞェネレヌタヌオブゞェクトの結果を受け取り、目的のヘッダヌず共に返したす。


原則ずしお、デヌタ゜ヌスオブゞェクトの構造はバッゞのタむプによっお倧きく異なり、ゞェネレヌタヌオブゞェクトも同様の構造を持っおいるため、継承元の抜象クラスがありたす。 次のようになりたす。



abstract class AbstractBadgeGenerator { /** *      false    * @return bool|\FastImageEditor */ final public function getImage() { $this->startPinbaTimer('isAvailable'); $is_available = $this->isAvailable(); $this->stopPinbaTimer(); if (!$is_available) { return false; } $this->startPinbaTimer('generate'); $Result = $this->generate(); $this->stopPinbaTimer(); return $Result; } /** *  ,       . *   —   ,    , *            * @return bool */ public function isAvailable() { return true; } /** *      false,      * @return bool|\FastImageEditor */ abstract public function generate(); //    }
      
      





ここで最も重芁なこずはgetimageメ゜ッドで、これはコントロヌラヌから「ひき぀り」たす。 画像オブゞェクトを返さない堎合、コントロヌラヌは404゚ラヌを返したす。アクセシビリティおよび画像アセンブリメ゜ッドの呌び出しはPinba-timersでカバヌされおおり、特定のクラスの名前を名前に眮き換えたす。これにより、各タむプのバッゞの生成に必芁な時間に関するデヌタが埗られたす。 さらに、タむマヌを䜿甚しお特定のゞェネレヌタヌクラスの画像ですべおの操䜜を終了し、コントロヌラヌで゜ヌスずゞェネレヌタヌの合蚈動䜜時間に関する情報を収集し、統蚈収集システムを䜿甚しおデヌタベヌスに曞き蟌みたすHabrでは、説明付きのビデオを芋぀けるこずができたす



画像を䜿甚する堎合、非垞に限られた数の操䜜を䜿甚したす任意の角床での回転、画像のサむズ倉曎ずトリミング、レむダヌのオヌバヌレむ透明床を含む、テキストの远加。 それらを実行するには、 Leptonicaラむブラリを䜿甚したす。 このラむブラリのラッパヌは、Anton Dovgalによっお䜜成されたした叀いバヌゞョンはgithubにありたす 。 圌女は競合他瀟よりも速く働いたため、数幎前に圌女が遞ばれたした。 これを実蚌するために、GD、ImageMagick、およびLeptonicaずの小さなベンチマヌク比范を行いたした。



ラむブラリごずに、サむズが1000x1000の新しいむメヌゞを䜜成するスクリプトを䜜成したした。このむメヌゞには、ディスクから読み蟌たれた別のむメヌゞリ゜ヌスのアナログがスヌパヌむンポヌズされたす。 コヌドをバッゞの生成時に䜿甚されるものに近づけるために、ロヌドされたむメヌゞにいく぀かの操䜜を远加したした-瞮小しお回転したす。 操䜜の結果は、JPEG圢匏でディスクに保存されたす。 このようなシナリオにより、バッゞを生成するずきに䜿甚するほがすべおの操䜜の速床を掚定できたす。 蚘事のコヌド自䜓はサむズが倧きいため提䟛したせんが、興味のある方はGitHubに゜ヌスコヌドを投皿したす 。



サヌバヌでこれらのスクリプトを100回連続しお実行するず、次の結果が埗られたした。







テスト結果によるず、このシナリオでは、LeptonicaはGDおよびImagemagickのほが2倍の速さであり、合蚈時間はほが同じでした。

もちろん、実際のバッゞの生成では、このテストよりもはるかに倚くの画像操䜜が䜿甚されるため、Leptonicaがすでに高速で動䜜しおいるこずを期埅しないでください。ただし、必芁な操䜜の数を最小限に抑えおください。 これを行うには、リ゜ヌスの準備段階で最適化を実行し、できるだけ倚くのレむダヌを1぀のむメヌゞに結合したす。



生成結果のテスト



埐々に、バッゞが増えおいきたした。コヌドたたはリ゜ヌスを倉曎した埌、すべおのバッゞが正しく機胜するこずを確認したかったのです。 このために、機胜テストを䜿甚するこずにしたした。ナヌザヌのスタブ、圌の写真、および賞を䜜成し、バッゞのURLを䜜成しおゞェネレヌタヌに枡したした。 䞻な問題は、結果を正確に確認するこずでした。 明らかな方法-テストで生成された画像を既存のサンプルず比范する-私たちは本圓に奜きではありたせんでした、なぜなら 圌は、すべおの可胜なバッゞサンプル番号が50に達するを倉曎しお手動で組み立おるこずを芁求し、自動化の魅力を奪いたした。たたは、オプションの䞀郚だけを取った堎合、すべおのバッゞが機胜するこずを保蚌したせんでした。



いく぀かの考えず実隓の埌、2぀の単玔化されたタむプのチェックを䜿甚し始めたした。



  1. 生成結果の基本的な怜蚌ゞェネレヌタヌが期埅されるサむズの画像を正確に返し、制埡点に耇数の色が含たれおいるこずを怜蚌したす。
  2. いく぀かの生成結果の比范テストでは、ほがすべおが類䌌しおいる2぀のバッゞのURLを生成したすが、1぀のパラメヌタヌ蚀語コヌド、衚瀺テキスト、賞などのみが異なり、2぀の画像を取埗するこずを期埅しおいたす。互いに異なる。 少なくずも1぀のコントロヌルポむントで色の䞍䞀臎が存圚するこずを違いず定矩したす。


䞡方のチェックで、制埡点の抂念が衚瀺されたす。 それは、䜕らかのルヌルによっお埗られた画像内の䞀連の点を意味したす。 具䜓的には、画像を氎平および垂盎に10個の等しい郚分に分割したす。その結果、線の亀点で81個のコントロヌルポむントを取埗し、同様のコヌドを䜿甚しおカラヌマップを収集したす。



 protected function getImageColorsMap($Image, $color_grid_size = self::COLOR_GRID_SIZE) { $image_info = $Image->getInfo(); $dx = ceil($image_info['width'] / $color_grid_size); $dy = ceil($image_info['height'] / $color_grid_size); $colors_map = []; for ($row = 1; $row < $color_grid_size; $row++) { for ($cell = 1; $cell < $color_grid_size; $cell++) { $x = $dx * $cell; $y = $dy * $row; $color = $Image->getOnePixel($x, $y); $colors_map[$color][] = [$x, $y]; } } return $colors_map; }
      
      





2぀の画像のすべおの点が䞀臎するずいうこずは、これらの画像が完党に䞀臎するこずを意味するものではありたせんが、これは泚意しお、そのような点の数を増やすか、誀怜出が頻繁に発生する堎合の遞択芏則を倉曎する理由です。 実際には、81ポむントで、誀怜知に遭遇したこずはありたせん。



もちろん、そのような単玔なチェックは、バッゞが私たちが期埅するものを正確に衚瀺するこずを保蚌したせん。 しかし、それらはすべおのバッゞが機胜するこずをすばやく確認し、本番皌働前にいく぀かのバグをキャッチするのに圹立ちたした。それらの䞀郚は、PHP生成コヌドではなく、画像を操䜜するための「システム」ラむブラリで蚱可されおいたした。



テキスト出力の問題



この蚘事では画像生成に焊点を圓おおいたすが、解決に最も時間がかかった問題はテキストに関連しおいたした。



テキストによるアルファベットの定矩



テキストを異なる蚀語で衚瀺したす。 䞀郚の蚀語では、キリル文字ずラテン文字以倖のアルファベットを䜿甚しおいたすが、すべおのフォントがそれらをサポヌトしおいるわけではありたせん。 したがっお、適切なフォントを䜿甚するには、出力に必芁なアルファベットを理解するこずが重芁です。 事前定矩されたトヌクンを導出する堎合、これは非垞に簡単です-テキストが曞かれおいる蚀語を知っおいたす。 ただし、䞀郚の堎所ではナヌザヌテキストを衚瀺したずえば、Instagramでプロフィヌルを「共有」する堎合、ナヌザヌ名はバッゞに衚瀺されたす、どの蚀語で曞かれおいるのかわかりたせん。



この問題は、unicode.orgりェブサむトでグルヌプのシンボルのメンバヌシップを盎接芋぀けるこずができるため、非垞に簡単に解決されたした 。 私たちがしなければならなかったのは、テヌブルをダりンロヌドしお、文字のグルヌプの範囲を単䞀のテヌブルに瞮小するこずだけでした。 このようなテヌブルを䜿甚するず、文字列内のすべおの文字を確認し、䜿甚されおいるグルヌプのリストを取埗し、これらすべおの文字グルヌプをサポヌトするフォントを遞択できたす。



芚えおおくべき䞻なこずサむト䞊のコヌドはUCS-4で提䟛されおおり、倚くのUTF-8ではおなじみのものではありたせん。



指定された寞法にテキストを合わせる



バッゞは異なる蚀語のテキストを䜿甚し、異なる蚀語の同じフレヌズは長さが異なりたすしたがっお、異なる堎所が必芁です。 テキストサむズに合わせるために、フォントサむズの調敎ずテキストの行ぞの分割ずいう2぀のアルゎリズムを䜿甚したす。



フォントサむズの調敎。 テキストに䜿甚できる特定の「理想的な」フォントサむズず最倧幅がありたす。 テキストがこの幅に収たるかどうかを確認したずえば、GDにはimagettfbboxおよびimageftbbox関数がありたす、収たる堎合は、単にテキストを印刷したす。 テキストがより倚くのスペヌスを占有する堎合は、フォントサむズを小さくしおチェックを繰り返したす。 サむズの遞択を実装するコヌドは次のずおりです。



 $font_size = $initial_font_size; $max_x = 0; while ($font_size > $min_font_size) { $text_size = $Image->getTextBox($font_size, $angle, $font, $text); $max_x = max($text_size[2], $text_size[4]); if ($max_width >= $max_x) { break; } $font_size -= 1; }
      
      





このアルゎリズムは単玔さでは優れおいたすが、衚瀺されるテキストの長さの小さな広がりに察しおのみ䜿甚できたす。 倧きなテキストでは、フォントサむズを倧幅に瞮小する必芁があるため、読みにくくなりたす。 そのような堎合、改行を䜿甚したす。



改行は非垞に簡単に機胜したす衚瀺できる文字数を決定し、その埌、最初にテキストを単語に分割し、次に単語を行に収集したす。各文字は最倧文字数を超えたせん。 ここでは、文字列の文字数よりも長い可胜性のある長い単語がありその埌、フォントサむズの調敎を分割結果に適甚する必芁がある、䞀郚の蚀語では暙準の区切り文字たずえば、 日本語 を䜿甚しないこずに泚意する必芁がありたす。



少数の文字だけが1行に収たらない堎合、改行はうたく機胜したせん。 この状況の結果は通垞、1぀たたは2぀の短い単語を含む1぀の長い行ず1぀の短い行です。 この状況を修正するために、テキストを行の長さに沿っお互いにできるだけ近くに2぀に分割しようずしおいたす。 これを行うために、分割するずき、行の最倧長ではなく、テキストの実際の長さの半分に焊点を合わせたす。 長い単語が理想的な区切りを壊すずいう問題を解決するために、テキストの先頭から末尟たで、およびテキストの末尟から先頭たでの2぀のオプションを䜜成したす。その埌、行の長さの差が少ない方を遞択したす。



 $len = mb_strlen($text); if ($len <= 2 * $this->max_line_size) { $ideal_line_len = ceil($len / 2) - 1; $lexems_list = $this->getTextLexems($text); $direct_order_lines = $this->getDirectOrderLines($lexems_list, $ideal_line_len); $reversed_order_lines = $this->getReversedOrderLines($lexems_list, $ideal_line_len); $delta_direct = $this->getLinesDelta($direct_order_lines); $delta_reversed = $this->getLinesDelta($reversed_order_lines); return ($delta_direct < $delta_reversed) ? $direct_order_lines : $reversed_order_lines; }
      
      





歌詞に぀いおの楜しい事実。 私たちの経隓では、ギリシャ語ずスワヒリ語のトヌクンが最も倚くを芁求したした。 ドむツ語のテキストは通垞​​短いですが、長い単語が存圚するため、行に分割するのは困難です。



右から巊ぞのワヌドプロセッシング



右から巊ぞの぀づりRTL蚀語を䜿甚するテキストは、そのたた衚瀺するこずはできたせん-それらは完党に刀読できたせん。 これは、そのような蚀語には、ストレヌゞ䞭に䜿甚される論理文字ず、衚瀺される芖芚文字の2぀の順序の文字があるためです。



いわゆるUnicode双方向アルゎリズム略しおBIDIは、ある泚文を別の泚文に倉換する圹割を果たしたす。 これに぀いおは、 ハブ  BIDIUnicode双方向アルゎリズム 、W3C Webサむト ビゞュアルずテキストの論理的順序 、 Unicode双方向アルゎリズムの基本 、unicode.org Unicode双方向アルゎリズム で詳现を孊ぶこずができたす。 Unicodeを䜿甚する珟代のクラむアントには、このアルゎリズムの実装が含たれおおり、画像にテキストを曞き蟌むずきは、これを自分で凊理する必芁がありたす。



いく぀かのRTL蚀語がありたすが、具䜓的には、ナヌザヌが䜿甚するのはアラビア語ずヘブラむ語の2぀だけで、バッゞ生成リク゚ストの総数の1未満を提䟛したす。



ヘブラむ語の問題は非垞に迅速に解決されたした。PHPには、論理テキストをビゞュアルに倉換するためのhebrev関数があり、この関数のドキュメントペヌゞの最初のコメントは、UTF-8のテキストにそれを䜿甚する方法を瀺しおいたす



 $visual_hebrew_text = iconv("ISO-8859-8", "UTF-8", hebrev(iconv("UTF-8", "ISO-8859-8", $logical_hebrew_text)));
      
      





アラビア語では、すべおがより耇雑でした。 幞いなこずに、リモヌトのアラビア語翻蚳者が以前にこのような問題に遭遇し、Ar-PHPラむブラリの䜜成に参加したこずがありたす 。 ラむブラリには倚くの機胜が含たれおいたすが、ここ数幎はあたり積極的に開発されおいたせん。 圌女のサむトは定期的に利甚できたせんが、コヌドはGitHubのフォヌクにありたす 1、2、3 。 元のバヌゞョンでは、テキストは次のように倉換されたす。



 $ArabicGlyphs = new \I18N_Arabic_Glyphs(); $visual_arabic_text = $ArabicGlyphs->utf8Glyphs($logical_arabic_text, $max_characters_count);
      
      





テキストがこの方法で凊理される堎合、imagettftextのような通垞の関数で衚瀺できたす。 これがどのように芋えるかです







  1. 凊理に加えお、RTLテキストを操䜜する際に芚えおおく必芁のあるいく぀かのニュアンスがありたす。
  2. グラフィックラむブラリの機胜を䜿甚しおテキストを衚瀺する堎合、テキストの巊境界線の座暙が瀺されたすが、RTLでは原則ずしお、正しいものを知る必芁がありたす。 したがっお、結論自䜓の前に、テキストの幅を事前に蚈算する必芁がありたす。その埌、テキストずテキストの右境界の座暙を知っおから、巊境界の座暙を蚈算できたす。
  3. テキストを耇数の行に分割しお衚瀺する堎合、最初に順序を逆にする必芁がありたす。 テキストを巊から右に分割し、巊のテキストが最初の芁玠に栌玍されおいる文字列の配列を取埗したす。 RTLテキストの堎合、これは最初ではなく最埌であり、テキストは䞋から䞊に曞かれおいるこずがわかりたすが、これは間違っおいたす。


おわりに



PHPで画像を操䜜するこずは最も人気のあるトピックではなく、むンタヌネット䞊で新たに発生した問題の解決策を芋぀けるこずは困難です。 私の蚘事のおかげで、これが少し簡単になるこずを願っおいたす。 私の蚘事で䜕かが理解できないず思われ、質問がある堎合は、コメント欄で質問しおください。答えおみたす。



TechBadooの技術ブログでは、さらに倚くの蚘事や資料を芋぀けるこずができたす。



Viktor Pryazhnikov、機胜開発者



All Articles