Add a method to read a stream without advancing it.
authorscroggo <scroggo@google.com>
Thu, 2 Apr 2015 20:19:51 +0000 (13:19 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 2 Apr 2015 20:19:51 +0000 (13:19 -0700)
Add a virtual method on SkStream which will do a "peek" some bytes, so
that those bytes are read, but the next call to read will be
unaffected.

Implement peek for SkMemoryStream, where the implementation is simple
and obvious.

Implement peek on SkFrontBufferedStream.

Add tests.

Motivated by decoding streams which cannot be rewound.

TBR=reed@google.com

BUG=skia:3257

Review URL: https://codereview.chromium.org/1044953002

include/core/SkStream.h
src/core/SkStream.cpp
src/utils/SkFrontBufferedStream.cpp
tests/StreamTest.cpp

index c227765..4c9c461 100644 (file)
@@ -63,6 +63,20 @@ public:
         return this->read(NULL, size);
     }
 
+    /**
+     *  Attempt to peek at size bytes.
+     *  If this stream supports peeking, and it can peek size bytes, copy size
+     *  bytes into buffer, and return true.
+     *  If the stream does not support peeking, or cannot peek size bytes,
+     *  return false and leave buffer unchanged.
+     *  The stream is guaranteed to be in the same visible state after this
+     *  call, regardless of success or failure.
+     *  @param buffer Must not be NULL. Destination to copy bytes.
+     *  @param size Number of bytes to copy.
+     *  @return Whether the peek was performed.
+     */
+    virtual bool peek(void* /* buffer */, size_t /* size */) const { return false; }
+
     /** Returns true when all the bytes in the stream have been read.
      *  This may return true early (when there are no more bytes to be read)
      *  or late (after the first unsuccessful read).
@@ -319,6 +333,8 @@ public:
     size_t read(void* buffer, size_t size) override;
     bool isAtEnd() const override;
 
+    bool peek(void* buffer, size_t size) const override;
+
     bool rewind() override;
     SkMemoryStream* duplicate() const override;
 
index 426f556..ad67a0b 100644 (file)
@@ -367,6 +367,20 @@ size_t SkMemoryStream::read(void* buffer, size_t size) {
     return size;
 }
 
+bool SkMemoryStream::peek(void* buffer, size_t size) const {
+    SkASSERT(buffer != NULL);
+    const size_t position = fOffset;
+    if (size > fData->size() - position) {
+        // The stream is not large enough to satisfy this request.
+        return false;
+    }
+    SkMemoryStream* nonConstThis = const_cast<SkMemoryStream*>(this);
+    SkDEBUGCODE(const size_t bytesRead =) nonConstThis->read(buffer, size);
+    SkASSERT(bytesRead == size);
+    nonConstThis->fOffset = position;
+    return true;
+}
+
 bool SkMemoryStream::isAtEnd() const {
     return fOffset == fData->size();
 }
index f23b1f9..beead37 100644 (file)
@@ -16,6 +16,8 @@ public:
 
     size_t read(void* buffer, size_t size) override;
 
+    bool peek(void* buffer, size_t size) const override;
+
     bool isAtEnd() const override;
 
     bool rewind() override;
@@ -155,6 +157,20 @@ size_t FrontBufferedStream::readDirectlyFromStream(char* dst, size_t size) {
     return bytesReadDirectly;
 }
 
+bool FrontBufferedStream::peek(void* dst, size_t size) const {
+    // Keep track of the offset so we can return to it.
+    const size_t start = fOffset;
+    if (start + size > fBufferSize) {
+        // This stream is not able to buffer enough.
+        return false;
+    }
+    FrontBufferedStream* nonConstThis = const_cast<FrontBufferedStream*>(this);
+    SkDEBUGCODE(const size_t bytesRead =) nonConstThis->read(dst, size);
+    SkASSERT(bytesRead == size);
+    nonConstThis->fOffset = start;
+    return true;
+}
+
 size_t FrontBufferedStream::read(void* voidDst, size_t size) {
     // Cast voidDst to a char* for easy addition.
     char* dst = reinterpret_cast<char*>(voidDst);
index ff22ecf..7a89c99 100644 (file)
@@ -5,7 +5,9 @@
  * found in the LICENSE file.
  */
 
+#include "Resources.h"
 #include "SkData.h"
+#include "SkFrontBufferedStream.h"
 #include "SkOSFile.h"
 #include "SkRandom.h"
 #include "SkStream.h"
@@ -190,3 +192,76 @@ DEF_TEST(Stream, reporter) {
     TestPackedUInt(reporter);
     TestNullData();
 }
+
+/**
+ *  Tests peeking and then reading the same amount. The two should provide the
+ *  same results.
+ *  Returns whether the stream could peek.
+ */
+static bool compare_peek_to_read(skiatest::Reporter* reporter,
+                                 SkStream* stream, size_t bytesToPeek) {
+    // The rest of our tests won't be very interesting if bytesToPeek is zero.
+    REPORTER_ASSERT(reporter, bytesToPeek > 0);
+    SkAutoMalloc peekStorage(bytesToPeek);
+    SkAutoMalloc readStorage(bytesToPeek);
+    void* peekPtr = peekStorage.get();
+    void* readPtr = peekStorage.get();
+
+    if (!stream->peek(peekPtr, bytesToPeek)) {
+        return false;
+    }
+    const size_t bytesRead = stream->read(readPtr, bytesToPeek);
+
+    // bytesRead should only be less than attempted if the stream is at the
+    // end.
+    REPORTER_ASSERT(reporter, bytesRead == bytesToPeek || stream->isAtEnd());
+
+    // peek and read should behave the same, except peek returned to the
+    // original position, so they read the same data.
+    REPORTER_ASSERT(reporter, !memcmp(peekPtr, readPtr, bytesRead));
+
+    return true;
+}
+
+static void test_peeking_stream(skiatest::Reporter* r, SkStream* stream, size_t limit) {
+    size_t peeked = 0;
+    for (size_t i = 1; !stream->isAtEnd(); i++) {
+        const bool couldPeek = compare_peek_to_read(r, stream, i);
+        if (!couldPeek) {
+            REPORTER_ASSERT(r, peeked + i > limit);
+            // No more peeking is supported.
+            break;
+        }
+        peeked += i;
+    }
+}
+
+static void test_peeking_front_buffered_stream(skiatest::Reporter* r,
+                                               const SkStream& original,
+                                               size_t bufferSize) {
+    SkStream* dupe = original.duplicate();
+    REPORTER_ASSERT(r, dupe != NULL);
+    SkAutoTDelete<SkStream> bufferedStream(SkFrontBufferedStream::Create(dupe, bufferSize));
+    REPORTER_ASSERT(r, bufferedStream != NULL);
+    test_peeking_stream(r, bufferedStream, bufferSize);
+}
+
+DEF_TEST(StreamPeek, reporter) {
+    // Test a memory stream.
+    const char gAbcs[] = "abcdefghijklmnopqrstuvwxyz";
+    SkMemoryStream memStream(gAbcs, strlen(gAbcs), false);
+    test_peeking_stream(reporter, &memStream, memStream.getLength());
+
+    // Test an arbitrary file stream. file streams do not support peeking.
+    SkFILEStream fileStream(GetResourcePath("baby_tux.webp").c_str());
+    REPORTER_ASSERT(reporter, fileStream.isValid());
+    SkAutoMalloc storage(fileStream.getLength());
+    for (size_t i = 1; i < fileStream.getLength(); i++) {
+        REPORTER_ASSERT(reporter, !fileStream.peek(storage.get(), i));
+    }
+
+    // Now test some FrontBufferedStreams
+    for (size_t i = 1; i < memStream.getLength(); i++) {
+        test_peeking_front_buffered_stream(reporter, memStream, i);
+    }
+}